重定向规则膨胀到几千条,构建变慢、爬虫绕圈

_redirects 文件三千多行,构建多花八秒,爬虫追多跳链路,没人记得哪些规则还需要。讲清楚怎么审计、合并和裁剪。

两年前 _redirects 只有 40 行,现在已经 3200 行了。每次 slug 改名、每次分类重整、每次发布后才发现的拼写错误,都加一行,从来没人删。结果构建多花 8 秒解析文件,边缘函数冷启动撞到内存上限,Search Console 里 “Page with redirect” 桶越涨越大——因为 Googlebot 在追 3 跳链路,最后一跳还是 410。重定向表本身已经变成了一笔技术债。

这篇文章讲清楚膨胀是怎么发生的、怎么安全审计和压平,以及哪些规则能防止它再次堆起来。

常见原因

按命中率从高到低排列。

1. 每次改 slug 都加了一条”向前”规则,从来没合并过

编辑把 /why-claude-is-better/ 改成 /claude-vs-gpt-comparison/,加了一条重定向。半年后又改成 /claude-4-vs-gpt-5/,又加一条。现在变成 3 跳:第 1 跳 → 第 2 跳 → 第 3 跳 → 200。

怎么判断:挑几个源 URL 跑 curl -sIL,如果最终 200 之前出现多个 Location 头,就是链路在跳。

2. 一次分类大改一次塞了几百条

/category/ai-tools/ 改成 /ai-applications/,这个 PR 一口气加了 400 条重定向,覆盖所有子 URL,但没有一条收成通配符。

怎么判断:grep -c "^/category/ai-tools" _redirects。如果数量在几百,而且每一行都指向 /ai-applications/ 下的对应子路径,那一条通配符就能取代。

3. 给 Google 早就忘掉的 URL 写的重定向

2023 年给一个 12 次访问、18 个月没被爬过的 URL 加了重定向。规则到今天还在每次部署里。

怎么判断:把重定向源路径跟过去 12 个月的 Search Console + 站内分析交叉对比。0 曝光 + 0 点击的源就是死重量。

4. 斜杠、www、协议归一化全塞在重定向表里

/foo/foo/http://https://www.、非 www. 每个组合都给每个页面单独写了重定向行。等于每个 URL 6 条规则,本该在 host 层一次解决。

怎么判断:同一个目标 URL 出现 4-6 次,源只差斜杠/host。

5. CMS 迁移时遗留下来、没人认领的旧规则

从 WordPress 迁出来时,把整个重定向插件的数据库导出来塞进去了。600 条”上一代上一代 CMS”的 URL,过去 5 年没有任何链接指向它们。

怎么判断:源路径不符合当前 URL 结构(/?p=1234/index.php?cat=.../wp-content/...)。

6. 重复规则,目标还不一样

同一个源路径出现两次,目标不一样。哪一条生效跟平台有关(Netlify 取首条,Cloudflare Workers 取末条)。

怎么判断:awk '{print $1}' _redirects | sort | uniq -d

7. 软 404:全部重定向到首页

页面删了,有人把它 301 到 /。结果几百个毫不相关的 URL 都跳到首页。Google 把这些识别成软 404,在 Search Console 里报”Submitted URL seems to be a Soft 404”。

怎么判断:grep -E " / 30[12]$" _redirects | wc -l,如果不只是个位数,就是模式不是例外。

最短修复路径

第 1 步:快照,版本化保留

动手前先把 _redirects 拷一份带日期的备份,方便对比和回滚:

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"

第 2 步:把所有规则解析到最终目标

把每条重定向一路追到底,写成 TSV。这是整个清理过程里最有用的一份产物。

// 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`);
}

任何 hops > 1 都是要压平的链路,任何 LOOP 是 bug,先修。

第 3 步:一次性把链路压平

把每条多跳规则直接改成指向最终目标。一般能砍掉 20-30% 的爬虫可见延迟,顺带把”链路尾巴是已删页面”造成的软 404 风险一起消掉。

第 4 步:批量规则改成通配符

主流边缘平台都支持通配。400 条同级重定向可以收成一条:

# 改之前
/category/ai-tools/chatgpt        /ai-applications/chatgpt        301
/category/ai-tools/claude         /ai-applications/claude         301
# ... 还有 398 条

# 改之后
/category/ai-tools/*              /ai-applications/:splat         301

删原始规则之前,先用 curl -sI 抽 5-10 个样本验一下。

第 5 步:0 流量 + 0 内链的规则直接淘汰

把重定向源跟以下四类信号对比:

  • Search Console:12 个月内有曝光吗?
  • 站内分析:12 个月内有会话吗?
  • 内链:站内还有任何地方链向这个 URL 吗?
  • 外链:外链工具里还有引荐来源吗?

四项全是”否”,且规则超过 12 个月,直接删。这个 URL 已经从可索引网络里消失了。

第 6 步:把归一化挪出重定向表

斜杠、host、协议这种归一化应该在边缘配置或框架配置里做,不是逐页写规则。Astro 里:

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

然后把 _redirects 里每一条逐页的斜杠变体都删掉。

第 7 步:加上行数上限和”有效期”

CI 加一道检查:_redirects 超过 N 行、或包含超过 M 个月没注释说明的规则,就让构建失败。

// scripts/check-redirects.mjs
const MAX_LINES = 800;
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);
}

配合一条注释约定:每条规则上方必须有 # added: YYYY-MM-DD reason: ...。每年一次例行审计,把陈旧的清掉。

哪些不该算在你头上

重定向表本质是过去若干年决策的账本。一部分膨胀是合法 URL 卫生的代价。目标不是”零规则”,而是”可审计且无链路”。如果业务确实四次重命名了大栏目,你就会有几百条规则,这没问题,只要每条链路已经压平、每条规则都有理由。

容易误判成

  • “构建慢是 MDX 的锅。“——其实重定向表解析往往才是热点,先 profile 再下结论。
  • “内容页爬取预算不够。“——Googlebot 把预算花在你的重定向表上,不在新内容上。
  • “边缘函数内存上限要升级。“——如果函数冷启动时把 _redirects 整个加载到内存,缩文件比升运行时便宜得多。

预防

  • 加新重定向的同一个 PR 里,把多跳链路压平(写个脚本,不是流程靠人记)。
  • 分类批量改名一律用通配符,不要手工枚举同级。
  • 每条规则注释 # added: YYYY-MM-DD reason:,以后才知道能不能删。
  • 季度任务:把超过 12 个月、0 曝光、0 点击、0 内链、0 外链的规则全删。
  • 协议/host/斜杠的归一化挪到边缘配置,不要逐页写。
  • CI 加行数上限,文件不能悄悄涨过阈值。

FAQ

  • 删旧重定向会不会伤 SEO? 只有当源 URL 还有曝光、内链、外链或者点击时才会。四个信号 12 个月都是 0 的,这个 URL 已经不在可索引网络里了,规则就是死重量。
  • slug 改名用 301 还是 302? 长期保留的改名用 301。302 只在真临时移动时用,内容站很少遇到。

相关

标签: #内容运营 #排查 #SEO #redirects #site-performance #抓取预算