Redirect Map Bloated to Thousands of Lines, Slowing Builds

A 3,000-line _redirects file slows builds, makes crawlers chase chains, and silently drops rules past your host's cap. How to audit, collapse chains, and prune safely.

Fastest fix: resolve every rule to its final destination, rewrite multi-hop chains to point straight at the end (one hop), and delete rules whose source URL has had zero impressions, zero clicks, zero inlinks, and zero backlinks for 12+ months. That alone usually cuts the file by half and removes the soft-404 risk. If you are on Cloudflare Pages, this is urgent: the _redirects file is capped at 2,000 static + 100 dynamic = 2,100 rules (verified June 2026), and a 3,200-line file means rules past the cap are silently ignored, so some of your “redirects” are not actually redirecting.

Two years ago your _redirects file had 40 entries. Today it has 3,200. Every slug rename, every taxonomy rework, every typo-fix on a published slug added a line, and nobody ever removed one. Now builds spend several extra seconds parsing the file, your edge host quietly drops rules past its limit, and Search Console’s “Page with redirect” bucket keeps growing because Googlebot is walking 3-hop chains that finally end at a 410. The redirect map has become its own technical-debt artifact.

This article covers how the bloat happens, how to audit and collapse it safely, and the rules that stop it from refilling.

Know your platform’s hard cap first

Before auditing, check what your host actually enforces. The number that matters is not “is the file big,” it is “is the file bigger than the cap, so rules are being dropped.” Verified June 2026:

Host_redirects rule capMatch orderOverflow behavior
Cloudflare Pages2,000 static + 100 dynamic = 2,100 total; each line <= 1,000 chars; static rules must come before dynamicTop-most matching rule winsRules past the cap are dropped; move overflow to account-level Bulk Redirects
NetlifyNo hard count, but a too-large serialized output (across _redirects + netlify.toml) fails the deploy; 10,000+ → use wildcards/placeholders or Edge FunctionsFirst match top-to-bottomDeploy fails or rule is ignored

A common myth: “the _redirects format takes the last matching rule on Cloudflare.” It does not. For the _redirects file on both Cloudflare Pages and Netlify, the top-most matching rule wins. Last-match behavior only happens if you hand-roll redirect logic in a Cloudflare Worker. So if you have duplicate sources, the one higher in the file is live.

Common causes

Ordered by hit rate, highest first.

1. Every slug rename added a forward rule, never a backward one

Editorial renamed /why-claude-is-better/ to /claude-vs-gpt-comparison/. A redirect went in. Six months later they renamed it again to /claude-4-vs-gpt-5/. Another redirect. Now there is a 3-hop chain: hop 1 -> hop 2 -> hop 3 -> 200.

How to spot it: run curl -sIL <old-url> on a sample of redirect sources. If you see more than one Location header before the final status, you have chains.

2. A bulk taxonomy rework dumped hundreds of rules in one PR

You renamed /category/ai-tools/ to /ai-applications/. The PR added 400 redirects covering every child URL. None of them were collapsed into a wildcard.

How to spot it: grep -c "^/category/ai-tools" _redirects. If the count is in the hundreds and every line maps to a sibling under /ai-applications/, one wildcard replaces them all.

3. Redirects for URLs Google has already forgotten

You added a redirect in 2023 for a URL that got 12 visits total and has not been crawled in 18 months. The rule still ships on every deploy.

How to spot it: cross-reference redirect source paths with the last 12 months of Search Console plus analytics. Sources with zero impressions and zero clicks are dead weight.

4. Trailing-slash, www, and protocol normalizations live in the map

Each of /foo, /foo/, http://, https://, www., and non-www. got its own redirect line per page. That is up to 6x rules per URL that should be a single host-level rule.

How to spot it: the same destination appears 4-6 times with only host or slash differences in the source.

5. Imported legacy redirects from a CMS migration nobody owns

When you migrated off WordPress, you exported the entire redirect-plugin DB. 600 rules of old-old-CMS URLs that nobody has linked to in 5 years.

How to spot it: source patterns inconsistent with your current URL structure (/?p=1234, /index.php?cat=..., /wp-content/...).

6. Duplicate rules with conflicting targets

The same source path appears twice with different destinations. On the _redirects file the top-most one wins (see the table above), so the lower rule is silently dead and misleading anyone who edits it later.

How to spot it: awk '{print $1}' _redirects | sort | uniq -d.

7. Soft-404 redirects to the homepage

When a page was deleted, somebody redirected it to /. Now hundreds of unrelated URLs all 301 to the homepage. Google treats these as soft-404s and reports “Submitted URL seems to be a soft 404.” A deleted page with no equivalent should return 410 Gone (or 404), not redirect to /.

How to spot it: grep -E " / 30[12]$" _redirects | wc -l. More than a handful means it is a pattern, not an exception.

Which bucket am I in?

Symptom you actually seeMost likely causeFix section
Some redirects just do not fire on productionFile exceeds host cap (Cloudflare 2,100), rules droppedCap table + Step 4/5 to shrink under cap
curl -sIL shows 2+ Location headersMulti-hop chains (#1)Step 3
Hundreds of identical destinationsBulk taxonomy rename (#2)Step 4
Hundreds of ... / 301 linesSoft-404 to homepage (#7)Return 410 instead
uniq -d lists many sourcesDuplicate conflicting rules (#6)De-dupe, keep top-most
”Page with redirect” climbing in Search ConsoleChains and dead rules wasting crawl budgetSteps 3 + 5

Shortest path to fix

Step 1: Snapshot and version the current map

Before touching anything, copy _redirects to a dated backup so you can compare and roll back:

cp public/_redirects public/_redirects.snapshot-$(date +%Y%m%d).txt
git add public/_redirects.snapshot-*.txt
git commit -m "snapshot: redirect map before audit"

Step 2: Resolve every rule to its final destination

Walk every redirect end-to-end and write the resolved chain to a TSV. This is the single most useful artifact in the whole cleanup.

// scripts/resolve-redirects.mjs
import fs from 'node:fs';
const lines = fs.readFileSync('public/_redirects', 'utf8').split('\n');
const map = new Map();
for (const line of lines) {
  const m = line.match(/^(\S+)\s+(\S+)\s+(\d+)/);
  if (m) map.set(m[1], { to: m[2], code: m[3] });
}
function resolve(path, hops = 0) {
  if (hops > 10) return { final: path, hops, loop: true };
  const rule = map.get(path);
  if (!rule) return { final: path, hops };
  return resolve(rule.to, hops + 1);
}
for (const [from] of map) {
  const r = resolve(from);
  process.stdout.write(`${from}\t${r.final}\t${r.hops}\t${r.loop ? 'LOOP' : ''}\n`);
}

Any row with hops > 1 is a chain to collapse. Any LOOP row is a bug; fix it immediately (a loop returns “too many redirects” to users and a redirect error in Search Console).

Step 3: Collapse chains in one pass

Rewrite every multi-hop rule to point directly at the final destination so it is a single hop. Google’s crawlers follow up to 10 hops, but Google’s own guidance is to keep chains to no more than 3 and ideally fewer (see the 301 redirects doc); each hop adds roughly 150-300 ms of latency for real users too. Collapsing also removes the soft-404 risk where a chain quietly ends at a deleted page.

Step 4: Replace bulk rules with wildcards

Both Cloudflare Pages and Netlify support splats. Use * in the source and :splat in the destination to replace 400 sibling redirects with one rule:

# Before
/category/ai-tools/chatgpt        /ai-applications/chatgpt        301
/category/ai-tools/claude         /ai-applications/claude         301
# ... 398 more

# After
/category/ai-tools/*              /ai-applications/:splat         301

On Cloudflare Pages, a splat rule counts as a dynamic redirect (capped at 100) and must appear after all static rules in the file, so collapsing bulk rules into splats both shrinks the file and keeps you under the static cap. Verify with curl -sI on 5-10 sample paths before deleting the originals.

Cross-reference redirect sources with four signals:

  • Search Console: any impressions in 12 months?
  • Analytics: any sessions in 12 months?
  • Internal links: does anything on the live site point at this URL?
  • Backlinks: any external referrer in your backlink tool?

If all four are “no” and the rule is older than 12 months, delete it. The URL is gone from the indexable web and only the final destination is ever indexed anyway, so the rule earns nothing.

Step 6: Move normalization off the redirect map

Trailing-slash, host, and protocol enforcement belongs in framework or edge config, not as per-page rules. In Astro, set the policy once:

// astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  trailingSlash: 'always',
  site: 'https://example.com',
});

Then handle www vs non-www and http vs https with one host-level rule at the edge (Cloudflare redirect rule or a single Netlify rule), and delete every per-page slash and host variant from _redirects.

Step 7: Lock in a cap and a TTL

Add a CI check that fails the build if _redirects exceeds your line cap. Set the cap below your host’s hard limit so you find out in CI, not in production:

// scripts/check-redirects.mjs
import fs from 'node:fs';
const MAX_LINES = 800; // well under Cloudflare's 2,100 hard cap
const lines = fs.readFileSync('public/_redirects', 'utf8').split('\n').filter(Boolean);
if (lines.length > MAX_LINES) {
  console.error(`Redirect map has ${lines.length} lines (cap: ${MAX_LINES})`);
  process.exit(1);
}

Pair it with a comment convention: every rule carries # added: YYYY-MM-DD reason: ... on the line above. An annual review removes anything stale.

How to confirm it is fixed

  • curl -sIL <old-url> on your former chain sources now shows exactly one Location header before a 200.
  • wc -l public/_redirects is comfortably under your host’s cap (and under your CI cap).
  • awk '{print $1}' public/_redirects | sort | uniq -d returns nothing (no duplicate sources).
  • After redeploy, spot-check 5-10 redirects you collapsed: each returns 301 straight to the final URL.
  • Over the next few weeks, the “Page with redirect” count in Search Console stops climbing.

When this is not on you

A redirect map is a ledger of past decisions. Some bloat is the cost of legitimate URL hygiene over years. The goal is not zero rules, it is “auditable, under the cap, and not chain-y.” If the business genuinely renamed sections four times, you will have hundreds of rules, and that is fine as long as the chains are collapsed and each rule is justified.

Easy to misdiagnose as

  • “Builds are slow because of MDX.” Often the redirect-map parse is the actual hot path; profile before you blame content.
  • “Crawl-budget problem on content pages.” When Googlebot reports many “Page with redirect” URLs, it is spending budget on your redirect map, not on new content.
  • “Edge function memory ceiling needs an upgrade.” If the function loads _redirects into memory on cold start, shrinking the file is cheaper than upsizing the runtime.
  • “A redirect just stopped working.” On Cloudflare Pages, the file likely crossed 2,100 rules and the host dropped the overflow; shrink the file or move bulk rules to account-level Bulk Redirects.

Prevention

  • Collapse multi-hop chains in the same PR that adds the new redirect (write a script, do not rely on a process).
  • Use wildcards for bulk taxonomy renames; never enumerate siblings by hand.
  • Annotate every rule with # added: YYYY-MM-DD reason: so future-you knows whether to delete it.
  • Quarterly job: drop any rule older than 12 months with zero impressions, zero clicks, zero inlinks, and zero backlinks.
  • Push protocol, host, and slash normalization to framework or edge config, never per-page.
  • CI line-count cap set below your host’s hard limit so the file cannot silently grow past it.

FAQ

  • Will deleting old redirects hurt SEO? Only if the source URL still has impressions, inlinks, or backlinks. When all four signals are zero for 12+ months, the URL is gone from the indexable web (only the final destination is ever indexed) and the rule is dead weight.
  • 301 vs 302 for slug renames? Use 301 (permanent) for renames you intend to keep; Google passes ranking signals to the target and drops the old URL from the index. Use 302 only for genuinely temporary moves, which on a content site is rare.
  • My redirect works locally but not in production. Why? On Cloudflare Pages the _redirects file caps at 2,100 rules and silently drops the overflow, and splat (dynamic) rules must come after all static rules. Check your line count and rule ordering first.
  • How many redirect hops will Google follow? Up to 10 in a chain, but Google recommends no more than 3 and ideally fewer than 5. Collapse to a single hop wherever you can.
  • Should a deleted page redirect to the homepage? No. If there is no equivalent page, return 410 Gone (or 404). Mass redirects to / are read as soft-404s and reported in Search Console.

Tags: #Content ops #Troubleshooting #SEO #redirects #site-performance #Crawl budget