{
  "id": "htwaDxt6lcMmER4U",
  "name": "Link Analyzer",
  "tags": [],
  "nodes": [
    {
      "id": "1c067fc0-5ff8-4a5c-b1f6-eea2435c7d4d",
      "name": "Verify HTTP",
      "type": "n8n-nodes-base.if",
      "position": [
        816,
        272
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "b3b6533e-1cb9-45d4-bed7-03c48a3a9b16",
              "operator": {
                "type": "string",
                "operation": "startsWith"
              },
              "leftValue": "={{ $('URL Analyzer Form').item.json.Link }}",
              "rightValue": "http"
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3
    },
    {
      "id": "b5a5602b-9f4b-4813-9db7-5a92d3d46202",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2352,
        736
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 288,
        "content": "## Display Error\n"
      },
      "typeVersion": 1
    },
    {
      "id": "e2178f4b-e5ce-40b4-a09a-2dabd85a64ea",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        768,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 592,
        "height": 480,
        "content": "## Add \"http\" to the link, if not present"
      },
      "typeVersion": 1
    },
    {
      "id": "0a7ad536-e1a1-4089-9439-76e95059a40d",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 592,
        "height": 480,
        "content": "## Capture Input and set production URL"
      },
      "typeVersion": 1
    },
    {
      "id": "20174f7f-e785-4dcf-a498-fe8445726dc1",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1360,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 992,
        "height": 480,
        "content": "## HTTP Request and Route"
      },
      "typeVersion": 1
    },
    {
      "id": "c014922a-a52a-4fcb-9abb-062471575798",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2352,
        240
      ],
      "parameters": {
        "color": 7,
        "width": 912,
        "height": 496,
        "content": "## Check for redirect and loops"
      },
      "typeVersion": 1
    },
    {
      "id": "4790bd10-afa1-4e50-9312-cfad7a7b98d9",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4096,
        496
      ],
      "parameters": {
        "color": 7,
        "width": 336,
        "height": 272,
        "content": "## Return to first page"
      },
      "typeVersion": 1
    },
    {
      "id": "1897cee3-ec5f-4919-8b93-3fe53f8c5f88",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -352,
        -144
      ],
      "parameters": {
        "width": 528,
        "height": 704,
        "content": "URL Analyzer\n\nThis workflow analyzes a URL by following HTTP redirects until the final destination is reached.\n\nFeatures\n\nSupports shortened URLs (bit.ly, tinyurl, t.ly, etc.)\nFollows HTTP redirects\nExtracts Open Graph metadata\nExtracts page title and meta description\nReturns the final destination URL\nHandles common HTTP errors\n\nRequirements\n\nNo external APIs\nNo API keys required\nUses only core n8n nodes\n\nSetup\n\n1. Activate the workflow.\n2. Open the **URL Analyzer Form** node.\n3. Copy its **Production URL**.\n4. Paste the URL into the **Set URL** node, replacing `REPLACE_WITH_PRODUCTION_FORM_URL`, so the \"Redirect to input page\" node redirects back to the input form.\n\n\nNotes\n\nThe workflow follows redirects until a final 200 OK response is received.\nConfigure a maximum redirect limit (recommended: 10) to avoid infinite redirect loops."
      },
      "typeVersion": 1
    },
    {
      "id": "73bda240-0b45-47e0-90e9-965096a99498",
      "name": "Set URL",
      "type": "n8n-nodes-base.set",
      "position": [
        544,
        272
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "dc5f25ff-443b-42b9-be1f-2299632c747c",
              "name": "url",
              "type": "string",
              "value": "REPLACE_WITH_PRODUCTION_FORM_URL"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "5395024c-7e23-4be6-ac75-e2f5c3abe51d",
      "name": "Code in JavaScript1",
      "type": "n8n-nodes-base.code",
      "position": [
        2528,
        320
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const nextUrl = $json.headers.location;\n\nconst visited = $json.visitedUrls ?? [];\nconst count = $json.redirectCount ?? 0;\n\nconst loopDetected = visited.includes(nextUrl);\n\nvisited.push(nextUrl);\n\nreturn {\n  json: {\n    ...$json,\n\n    Link: nextUrl,\n\n    visitedUrls: visited,\n\n    redirectCount: count + 1,\n\n    loopDetected\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "3c4b3b58-14f8-4ea5-908c-daa5b81b843e",
      "name": "Show Results",
      "type": "n8n-nodes-base.form",
      "position": [
        3776,
        160
      ],
      "parameters": {
        "options": {
          "buttonLabel": "Finish",
          "formDescription": "=Final URL: {{ $json.seo.canonical }}\n\nTitle: {{ $json.page.title }}\n\nDescription: {{ $json.page.description }}\n\nLanguage: {{ $json.page.language }}\n\nHas OpenGraph: {{ $json.hasOpenGraph }}\n\nHas Twitter Cards: {{ $json.hasTwitterCards }}\n\n"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1348a9c8-7d36-4f3d-8a3d-d58c20d70773",
      "name": "Link redirect",
      "type": "n8n-nodes-base.set",
      "position": [
        3088,
        528
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ce816c46-f881-46c3-b64f-f32b63881b1b",
              "name": "=Link",
              "type": "string",
              "value": "={{$json.headers.location}}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "bc5472d1-b86b-43e9-9ae3-69ae8b9f8009",
      "name": "Show redirects and loops",
      "type": "n8n-nodes-base.form",
      "position": [
        3088,
        304
      ],
      "parameters": {
        "options": {
          "formTitle": "Error",
          "buttonLabel": "Finish",
          "formDescription": "=Error: \n\nRedirects: {{ $json.redirectCount }}\nLoop detected: {{ $json.loopDetected }}"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "f67f23e5-befe-4c38-9c22-ae8f69f80b1c",
      "name": "URL Analyzer Form",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        272,
        272
      ],
      "parameters": {
        "options": {},
        "formTitle": "Link Analyzer",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Link",
              "placeholder": "Link"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "0b567144-02c0-4c1d-9c95-5e2221456013",
      "name": "Normalize URL adding HTTP",
      "type": "n8n-nodes-base.set",
      "position": [
        1056,
        368
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1c673d93-a34a-455f-a0a3-d935538e7c9b",
              "name": "Link",
              "type": "string",
              "value": "=http://{{ $('URL Analyzer Form').item.json.Link }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "750e5d0c-bca5-4054-94b0-2db9ee1da1f6",
      "name": "Normalize URL",
      "type": "n8n-nodes-base.set",
      "position": [
        1056,
        176
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1c673d93-a34a-455f-a0a3-d935538e7c9b",
              "name": "Link",
              "type": "string",
              "value": "={{ $('URL Analyzer Form').item.json.Link }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "dc5c13cd-b0da-47e2-86a1-e7256ac9d47e",
      "name": "Fetch URL",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        1536,
        256
      ],
      "parameters": {
        "url": "={{ $json.Link }}",
        "options": {
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "325ed770-f57c-49bf-8332-2360f15b0044",
      "name": "Check HTTP Status",
      "type": "n8n-nodes-base.switch",
      "position": [
        2048,
        176
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "200",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "36f7d022-005a-4f12-b6bd-829c90f76759",
                    "operator": {
                      "type": "number",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.statusCode }}",
                    "rightValue": 200
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "301",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "8d148b8a-92b1-4a56-8cb8-506527b7e128",
                    "operator": {
                      "type": "number",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.statusCode }}",
                    "rightValue": 301
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "302",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "0dd97cca-6ed2-4c88-aafa-2cebbbe33a7e",
                    "operator": {
                      "type": "number",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.statusCode }}",
                    "rightValue": 302
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "307",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "f10c471d-6183-4552-a67a-e0aa151114e5",
                    "operator": {
                      "type": "number",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.statusCode }}",
                    "rightValue": "=307"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "308",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "c2b9554b-1407-4122-9c5a-353585d1eb7a",
                    "operator": {
                      "type": "number",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.statusCode }}",
                    "rightValue": 308
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        },
        "looseTypeValidation": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "f15de3d8-343e-487e-b4b5-870a86015fe1",
      "name": "Update Redirect State",
      "type": "n8n-nodes-base.if",
      "position": [
        2736,
        320
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "c7380b3a-ad44-44a0-b373-a7127b9d903e",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.redirectCount }}",
              "rightValue": 10
            },
            {
              "id": "a77fe9cb-fd5d-4f04-b608-1615bbd9b623",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.loopDetected }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "8708ecea-f6d5-43d3-833f-68958d68b6e6",
      "name": "Extract Page Metadata",
      "type": "n8n-nodes-base.code",
      "position": [
        3408,
        160
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// ---------- Input ----------\n\nconst html = typeof $json.data === \"string\" ? $json.data : \"\";\n\nif (!html) {\n\treturn {\n\t\tjson: {\n\t\t\terror: \"No HTML content found.\",\n\t\t\tstatusCode: $json.statusCode ?? null\n\t\t}\n\t};\n}\n\n// ---------- Helpers ----------\n\nfunction decodeEntities(str) {\n\tif (!str) return null;\n\n\treturn str\n\t\t.replace(/&amp;/g, \"&\")\n\t\t.replace(/&lt;/g, \"<\")\n\t\t.replace(/&gt;/g, \">\")\n\t\t.replace(/&quot;/g, '\"')\n\t\t.replace(/&#39;/g, \"'\")\n\t\t.replace(/&nbsp;/g, \" \")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n}\n\nfunction extractAttribute(tag, attribute) {\n\tconst regex = new RegExp(`${attribute}=[\"']([^\"']+)[\"']`, \"i\");\n\tconst match = tag.match(regex);\n\treturn match ? match[1] : null;\n}\n\n// ---------- Parse all META tags only once ----------\n\nconst meta = {};\n\nconst metaTags = html.match(/<meta\\b[^>]*>/gi) || [];\n\nfor (const tag of metaTags) {\n\n\tconst key =\n\t\textractAttribute(tag, \"property\") ||\n\t\textractAttribute(tag, \"name\");\n\n\tif (!key) continue;\n\n\tconst content = extractAttribute(tag, \"content\");\n\n\tmeta[key.toLowerCase()] = decodeEntities(content);\n}\n\n// ---------- Extract generic tags ----------\n\nfunction extract(regex) {\n\tconst match = html.match(regex);\n\treturn match ? decodeEntities(match[1]) : null;\n}\n\nconst title = extract(/<title[^>]*>([\\s\\S]*?)<\\/title>/i);\n\nconst canonical = extract(\n\t/<link[^>]*rel=[\"']canonical[\"'][^>]*href=[\"']([^\"']+)[\"']/i\n);\n\nconst favicon =\n\textract(\n\t\t/<link[^>]*rel=[\"']icon[\"'][^>]*href=[\"']([^\"']+)[\"']/i\n\t) ||\n\textract(\n\t\t/<link[^>]*rel=[\"']shortcut icon[\"'][^>]*href=[\"']([^\"']+)[\"']/i\n\t);\n\nconst language = extract(\n\t/<html[^>]*lang=[\"']([^\"']+)[\"']/i\n);\n\nconst charset =\n\textract(\n\t\t/<meta[^>]*charset=[\"']?([^\"'>\\s]+)/i\n\t);\n\n// ---------- Final URL ----------\n\nlet finalUrl =\n\tmeta[\"og:url\"] ||\n\t$json.headers?.location ||\n\tnull;\n\nlet hostname = null;\n\ntry {\n\n\tif (finalUrl) {\n\t\thostname = new URL(finalUrl).hostname;\n\t}\n\n} catch {}\n\n// ---------- Tracking Parameters ----------\n\nlet tracking = {};\n\ntry {\n\n\tif (finalUrl) {\n\n\t\tconst params = new URL(finalUrl).searchParams;\n\n\t\tfor (const key of [\n\t\t\t\"utm_source\",\n\t\t\t\"utm_medium\",\n\t\t\t\"utm_campaign\",\n\t\t\t\"utm_term\",\n\t\t\t\"utm_content\",\n\t\t\t\"fbclid\",\n\t\t\t\"gclid\",\n\t\t\t\"msclkid\"\n\t\t]) {\n\n\t\t\tif (params.has(key)) {\n\t\t\t\ttracking[key] = params.get(key);\n\t\t\t}\n\n\t\t}\n\n\t}\n\n} catch {}\n\n// ---------- Return ----------\n\nreturn {\n\n\tjson: {\n\n\t\thttp: {\n\n\t\t\tstatusCode: $json.statusCode ?? null,\n\n\t\t\tstatusMessage: $json.statusMessage ?? null,\n\n\t\t\tcontentType: $json.headers?.[\"content-type\"] ?? null,\n\n\t\t\tserver: $json.headers?.server ?? null\n\n\t\t},\n\n\t\tpage: {\n\n\t\t\ttitle,\n\n\t\t\tdescription: meta[\"description\"],\n\n\t\t\tlanguage,\n\n\t\t\tcharset,\n\n\t\t\trobots: meta[\"robots\"],\n\n\t\t\tviewport: meta[\"viewport\"],\n\n\t\t\tauthor: meta[\"author\"],\n\n\t\t\tthemeColor: meta[\"theme-color\"]\n\n\t\t},\n\n\t\tseo: {\n\n\t\t\tcanonical,\n\n\t\t\tdomain: hostname\n\n\t\t},\n\n\t\topenGraph: {\n\n\t\t\ttitle: meta[\"og:title\"],\n\n\t\t\tdescription: meta[\"og:description\"],\n\n\t\t\turl: meta[\"og:url\"],\n\n\t\t\timage: meta[\"og:image\"],\n\n\t\t\ttype: meta[\"og:type\"],\n\n\t\t\tsiteName: meta[\"og:site_name\"]\n\n\t\t},\n\n\t\ttwitter: {\n\n\t\t\ttitle: meta[\"twitter:title\"],\n\n\t\t\tdescription: meta[\"twitter:description\"],\n\n\t\t\timage: meta[\"twitter:image\"],\n\n\t\t\tcard: meta[\"twitter:card\"]\n\n\t\t},\n\n\t\ttracking,\n\n\t\thasOpenGraph: Object.keys(meta).some(k => k.startsWith(\"og:\")),\n\n\t\thasTwitterCards: Object.keys(meta).some(k => k.startsWith(\"twitter:\"))\n\n\t}\n\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "d9038d28-2505-4883-8be8-a87eb2f3e78d",
      "name": "Error",
      "type": "n8n-nodes-base.form",
      "position": [
        2528,
        832
      ],
      "parameters": {
        "options": {
          "formTitle": "Error",
          "buttonLabel": "Finish",
          "formDescription": "=Error: {{ $json.statusCode }} \n{{ $json.statusMessage }}\n{{ $json.body.status }}\n{{ $json.body.detail }}\n{{ $json.error.message }}"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "718f08ba-dbc8-4d7b-995d-628fda054293",
      "name": "Redirect to input page",
      "type": "n8n-nodes-base.form",
      "position": [
        4208,
        592
      ],
      "parameters": {
        "options": {},
        "operation": "completion",
        "redirectUrl": "={{ $('Set URL').item.json.url }}",
        "respondWith": "redirect"
      },
      "typeVersion": 2.3
    },
    {
      "id": "31d00c1e-c1d9-489f-a509-3e1d8b815bb4",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3264,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 784,
        "height": 272,
        "content": "## Extract metadata and display results"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "16675c54-8bd5-4b16-8643-57e7d516ffc7",
  "connections": {
    "Error": {
      "main": [
        [
          {
            "node": "Redirect to input page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set URL": {
      "main": [
        [
          {
            "node": "Verify HTTP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch URL": {
      "main": [
        [
          {
            "node": "Check HTTP Status",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify HTTP": {
      "main": [
        [
          {
            "node": "Normalize URL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Normalize URL adding HTTP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Show Results": {
      "main": [
        [
          {
            "node": "Redirect to input page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Link redirect": {
      "main": [
        [
          {
            "node": "Fetch URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize URL": {
      "main": [
        [
          {
            "node": "Fetch URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check HTTP Status": {
      "main": [
        [
          {
            "node": "Extract Page Metadata",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code in JavaScript1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code in JavaScript1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code in JavaScript1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code in JavaScript1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "URL Analyzer Form": {
      "main": [
        [
          {
            "node": "Set URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript1": {
      "main": [
        [
          {
            "node": "Update Redirect State",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Page Metadata": {
      "main": [
        [
          {
            "node": "Show Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Redirect State": {
      "main": [
        [
          {
            "node": "Show redirects and loops",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Link redirect",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Show redirects and loops": {
      "main": [
        [
          {
            "node": "Redirect to input page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize URL adding HTTP": {
      "main": [
        [
          {
            "node": "Fetch URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}