[{"data":1,"prerenderedAt":492},["ShallowReactive",2],{"blog-\u002Fblog\u002Fredirect-chains-the-chain-is-the-finding":3,"blog-more-\u002Fblog\u002Fredirect-chains-the-chain-is-the-finding":467},{"id":4,"title":5,"author":6,"body":7,"cover":457,"date":458,"description":459,"extension":460,"meta":461,"navigation":333,"path":462,"readingTime":337,"seo":463,"stem":464,"tag":465,"__hash__":466},"blog\u002Fblog\u002Fredirect-chains-the-chain-is-the-finding.md","Redirect chains: the chain is the finding","The CrawlStat team",{"type":8,"value":9,"toc":444},"minimark",[10,14,22,63,66,71,100,107,111,114,117,124,147,162,166,169,234,237,243,247,288,292,297,304,364,372,376,379,399,403,406,413,417,420,424,440],[11,12,13],"p",{},"Nobody sets out to build a redirect chain. They build up over time.",[11,15,16,17,21],{},"The site moves to HTTPS, so someone adds a rule. Later the ",[18,19,20],"code",{},"www"," subdomain becomes canonical, so someone adds another. A redesign standardises on trailing slashes. A CMS migration brings its own redirect table. Each rule made sense on the day it was written. Put them together and an old link takes this route:",[23,24,29],"pre",{"className":25,"code":26,"language":27,"meta":28,"style":28},"language-txt shiki shiki-themes github-light","http:\u002F\u002Fexample.com\u002Fpricing\n  → 301  https:\u002F\u002Fexample.com\u002Fpricing\n  → 301  https:\u002F\u002Fwww.example.com\u002Fpricing\n  → 301  https:\u002F\u002Fwww.example.com\u002Fpricing\u002F\n  → 200\n","txt","",[18,30,31,39,45,51,57],{"__ignoreMap":28},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,38],{},"http:\u002F\u002Fexample.com\u002Fpricing\n",[32,40,42],{"class":34,"line":41},2,[32,43,44],{},"  → 301  https:\u002F\u002Fexample.com\u002Fpricing\n",[32,46,48],{"class":34,"line":47},3,[32,49,50],{},"  → 301  https:\u002F\u002Fwww.example.com\u002Fpricing\n",[32,52,54],{"class":34,"line":53},4,[32,55,56],{},"  → 301  https:\u002F\u002Fwww.example.com\u002Fpricing\u002F\n",[32,58,60],{"class":34,"line":59},5,[32,61,62],{},"  → 200\n",[11,64,65],{},"That's three hops before the visitor gets a page. Every one of them is a full round trip.",[67,68,70],"h2",{"id":69},"why-chains-matter","Why chains matter",[72,73,74,82,88,94],"ul",{},[75,76,77,81],"li",{},[78,79,80],"strong",{},"Latency."," Every hop is another request and response before the page can even start loading. On a slow mobile connection, you feel it.",[75,83,84,87],{},[78,85,86],{},"Crawlers give up."," Search engine crawlers follow redirects, but not forever. A long chain risks being abandoned before the destination is reached.",[75,89,90,93],{},[78,91,92],{},"Link value leaks."," A link that goes through redirects still works, but some of its value gets diluted along the way.",[75,95,96,99],{},[78,97,98],{},"They hide mistakes."," A chain that ends in a 404 is a broken link that looks like it works until you follow it all the way.",[11,101,102,103,106],{},"A ",[78,104,105],{},"redirect loop"," is the extreme case: A redirects to B, and B redirects back to A. Nothing is ever served. Browsers show an error after a few hops, and search engines give up on the URL.",[67,108,110],{"id":109},"why-crawlstat-follows-redirects-by-hand","Why CrawlStat follows redirects by hand",[11,112,113],{},"This part sounds like an implementation detail, but it's the heart of the matter.",[11,115,116],{},"Every decent HTTP client can follow redirects for you. You ask for a URL and get back the final response. That's exactly what a browser wants. For an SEO crawler, it's useless, because the client quietly throws away the hops it went through. You learn where you ended up, but not how you got there.",[11,118,119,120,123],{},"For an SEO crawler, ",[78,121,122],{},"the chain is the finding",". \"This URL takes three hops to reach a 200\" is an audit. \"These two URLs redirect to each other\" is an audit. So CrawlStat's HTTP client has redirect following turned off, and the engine walks the chain itself:",[125,126,127,134,137,144],"ol",{},[75,128,129,130,133],{},"Request the URL. If the response is a 3xx with a ",[18,131,132],{},"Location"," header, resolve that location (relative ones are legal and common).",[75,135,136],{},"Record the hop: hop number, from, to, and status code.",[75,138,139,140,143],{},"If the next URL has already appeared in this chain, stop and mark it as a ",[78,141,142],{},"loop"," instead of spinning forever.",[75,145,146],{},"Otherwise, follow it and repeat. After 10 redirects, give up and report the URL as unfetchable.",[11,148,149,150,153,154,157,158,161],{},"Every hop gets stored in the crawl database. Each URL row then carries two status codes: ",[18,151,152],{},"status_code",", the 3xx the URL itself returned, and ",[18,155,156],{},"final_status_code",", what the end of the chain returned. That difference is the point. A crawler that only reports \"200\" for ",[18,159,160],{},"http:\u002F\u002Fexample.com\u002Fpricing"," is hiding your problem from you.",[67,163,165],{"id":164},"what-crawlstat-reports","What CrawlStat reports",[11,167,168],{},"Three audits cover redirects:",[170,171,172,188],"table",{},[173,174,175],"thead",{},[176,177,178,182,185],"tr",{},[179,180,181],"th",{},"Audit",[179,183,184],{},"Severity",[179,186,187],{},"Triggers when",[189,190,191,207,221],"tbody",{},[176,192,193,201,204],{},[194,195,196],"td",{},[197,198,200],"a",{"href":199},"\u002Fdocs\u002Faudits\u002Fredirect-loops","Redirect loop",[194,202,203],{},"Error",[194,205,206],{},"A URL's redirects come back to a URL already in the chain",[176,208,209,215,218],{},[194,210,211],{},[197,212,214],{"href":213},"\u002Fdocs\u002Faudits\u002Fredirect-chains","Redirect chain",[194,216,217],{},"Warning",[194,219,220],{},"A URL takes more than two hops to reach its destination",[176,222,223,229,231],{},[194,224,225],{},[197,226,228],{"href":227},"\u002Fdocs\u002Faudits\u002Finternal-links-to-redirects","Internal link to a redirect",[194,230,217],{},[194,232,233],{},"One of your own pages links to a URL that redirects",[11,235,236],{},"The third one is often the most valuable. The first two tell you which redirects are bad. The third tells you which pages are sending traffic into them.",[238,239],"screenshot",{"alt":240,"caption":241,"src":242},"The CrawlStat Issues tab ranking findings by severity, with impact, reach and effort for the selected issue","Each issue is scored for impact, reach and effort, with the fix and the best linked pages it affects.","\u002Fscreenshots\u002Fissues-dark-ember.png",[67,244,246],{"id":245},"finding-them-in-the-app","Finding them in the app",[125,248,249,262,274,281],{},[75,250,251,252,255,256,258,259,261],{},"After a crawl, open ",[78,253,254],{},"Issues"," and click ",[78,257,214],{}," or ",[78,260,200],{},". The results table filters down to the affected URLs.",[75,263,264,265,269,270,273],{},"Select a row and press ",[266,267,268],"kbd",{},"Enter",". The detail panel shows the ",[78,271,272],{},"redirect chain"," hop by hop, with the status of each one.",[75,275,276,277,280],{},"Open the ",[78,278,279],{},"Inlinks"," panel to see which pages link to that URL. Those are the pages you'll edit.",[75,282,283,284,287],{},"For an overview, the ",[78,285,286],{},"Response codes"," tab shows status, final status and the number of redirects followed side by side. Any count above two is highlighted.",[67,289,291],{"id":290},"how-to-fix-them","How to fix them",[293,294,296],"h3",{"id":295},"collapse-chains-to-one-hop","Collapse chains to one hop",[11,298,299,300,303],{},"Change each redirect so it points straight at the final destination, not at the next hop. The cleanest way is to put all canonicalisation (scheme, host, trailing slash) in ",[78,301,302],{},"one rule in one place",", so every URL is rewritten at most once:",[23,305,307],{"className":25,"code":306,"language":27,"meta":28,"style":28},"# Before: three rules, applied in sequence\nhttp:\u002F\u002Fexample.com\u002Fpricing    → https:\u002F\u002Fexample.com\u002Fpricing\nhttps:\u002F\u002Fexample.com\u002Fpricing   → https:\u002F\u002Fwww.example.com\u002Fpricing\nhttps:\u002F\u002Fwww.example.com\u002Fpricing → https:\u002F\u002Fwww.example.com\u002Fpricing\u002F\n\n# After: every variant goes straight to the canonical form\nhttp:\u002F\u002Fexample.com\u002Fpricing      → https:\u002F\u002Fwww.example.com\u002Fpricing\u002F\nhttps:\u002F\u002Fexample.com\u002Fpricing     → https:\u002F\u002Fwww.example.com\u002Fpricing\u002F\nhttp:\u002F\u002Fwww.example.com\u002Fpricing  → https:\u002F\u002Fwww.example.com\u002Fpricing\u002F\nhttps:\u002F\u002Fwww.example.com\u002Fpricing → https:\u002F\u002Fwww.example.com\u002Fpricing\u002F\n",[18,308,309,314,319,324,329,335,341,347,353,359],{"__ignoreMap":28},[32,310,311],{"class":34,"line":35},[32,312,313],{},"# Before: three rules, applied in sequence\n",[32,315,316],{"class":34,"line":41},[32,317,318],{},"http:\u002F\u002Fexample.com\u002Fpricing    → https:\u002F\u002Fexample.com\u002Fpricing\n",[32,320,321],{"class":34,"line":47},[32,322,323],{},"https:\u002F\u002Fexample.com\u002Fpricing   → https:\u002F\u002Fwww.example.com\u002Fpricing\n",[32,325,326],{"class":34,"line":53},[32,327,328],{},"https:\u002F\u002Fwww.example.com\u002Fpricing → https:\u002F\u002Fwww.example.com\u002Fpricing\u002F\n",[32,330,331],{"class":34,"line":59},[32,332,334],{"emptyLinePlaceholder":333},true,"\n",[32,336,338],{"class":34,"line":337},6,[32,339,340],{},"# After: every variant goes straight to the canonical form\n",[32,342,344],{"class":34,"line":343},7,[32,345,346],{},"http:\u002F\u002Fexample.com\u002Fpricing      → https:\u002F\u002Fwww.example.com\u002Fpricing\u002F\n",[32,348,350],{"class":34,"line":349},8,[32,351,352],{},"https:\u002F\u002Fexample.com\u002Fpricing     → https:\u002F\u002Fwww.example.com\u002Fpricing\u002F\n",[32,354,356],{"class":34,"line":355},9,[32,357,358],{},"http:\u002F\u002Fwww.example.com\u002Fpricing  → https:\u002F\u002Fwww.example.com\u002Fpricing\u002F\n",[32,360,362],{"class":34,"line":361},10,[32,363,328],{},[11,365,366,367,371],{},"When you migrate URLs, update the ",[368,369,370],"em",{},"old"," redirect rules to point at the new destination. Don't add a new hop on top of them.",[293,373,375],{"id":374},"then-stop-linking-to-redirects-at-all","Then stop linking to redirects at all",[11,377,378],{},"A redirect should only ever serve external traffic and old bookmarks. Everything you control should name the destination directly:",[72,380,381,387,393],{},[75,382,383,386],{},[78,384,385],{},"Internal links",", especially in templates. A single navigation or footer link to an old URL can account for thousands of wasted requests across a site, which makes this one of the cheapest wins there is.",[75,388,389,392],{},[78,390,391],{},"Your XML sitemap."," It should list final URLs that return 200, nothing else.",[75,394,395,398],{},[78,396,397],{},"Canonical tags."," A canonical pointing at a redirect sends search engines a mixed signal. Point it at the URL that returns 200 directly.",[293,400,402],{"id":401},"break-loops-at-the-source","Break loops at the source",[11,404,405],{},"Loops nearly always come from two rules that disagree. The classic is a rule that sends HTTP to HTTPS fighting a trailing slash rule, or a CMS redirect duplicating one already on the server. Find the rules that point at each other, decide which layer owns canonicalisation, and remove the duplicate.",[407,408,410],"callout",{"type":409},"tip",[11,411,412],{},"If your CDN, your web server and your CMS can all issue redirects, choose one of them to own canonicalisation (scheme, host, trailing slash) and turn it off in the other two. Most loops we've seen come from two layers each trying to be helpful.",[293,414,416],{"id":415},"crawl-again-to-confirm","Crawl again to confirm",[11,418,419],{},"Redirect rules are easy to get almost right. After you deploy a fix, crawl again and check that the chain and loop counts went to zero, and that the list of internal links to redirects got shorter.",[67,421,423],{"id":422},"try-it-on-your-site","Try it on your site",[11,425,426,427,431,432,435,436,439],{},"Redirect audits are part of every crawl, and CrawlStat is free and open source, with no limit on the number of URLs. ",[197,428,430],{"href":429},"\u002Fdownload","Download CrawlStat",", crawl your site, and open Issues. And if you'd like the full explanation for each audit, the docs for ",[197,433,434],{"href":213},"redirect chains"," and ",[197,437,438],{"href":199},"redirect loops"," have it.",[441,442,443],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":28,"searchDepth":41,"depth":41,"links":445},[446,447,448,449,450,456],{"id":69,"depth":41,"text":70},{"id":109,"depth":41,"text":110},{"id":164,"depth":41,"text":165},{"id":245,"depth":41,"text":246},{"id":290,"depth":41,"text":291,"children":451},[452,453,454,455],{"id":295,"depth":47,"text":296},{"id":374,"depth":47,"text":375},{"id":401,"depth":47,"text":402},{"id":415,"depth":47,"text":416},{"id":422,"depth":41,"text":423},null,"2026-10-07","How redirect chains and loops build up, why CrawlStat follows every redirect by hand instead of letting the HTTP client do it, and a practical routine for collapsing them.","md",{},"\u002Fblog\u002Fredirect-chains-the-chain-is-the-finding",{"title":5,"description":459},"blog\u002Fredirect-chains-the-chain-is-the-finding","SEO guide","6XkFOfFyLdiB7LxF5-QkZtC7kUJI-tg_hN9amk6xJsU",[468,473,478,482,487,488],{"title":469,"path":470,"date":471,"tag":472,"readingTime":59},"CrawlStat is now free and open source","\u002Fblog\u002Fcrawlstat-is-open-source","2026-11-04","News",{"title":474,"path":475,"date":476,"tag":477,"readingTime":53},"Client reports, JavaScript rendering and a new way around a crawl","\u002Fblog\u002Fclient-reports-and-javascript-rendering","2026-10-26","Announcement",{"title":479,"path":480,"date":481,"tag":477,"readingTime":337},"Introducing CrawlStat","\u002Fblog\u002Fintroducing-crawlstat","2026-10-21",{"title":483,"path":484,"date":485,"tag":486,"readingTime":337},"The database is the frontier: how CrawlStat keeps memory flat","\u002Fblog\u002Fflat-memory-crawling","2026-10-14","Engineering",{"title":5,"path":462,"date":458,"tag":465,"readingTime":337},{"title":489,"path":490,"date":491,"tag":486,"readingTime":59},"Licensing without a database","\u002Fblog\u002Flicensing-without-a-database","2026-09-30",1793827598363]