部署后重定向不生效:3 个原因 + 修复路径

vercel.json 或 _redirects 里配的 301 部署后没生效,多半是规则没进 build、被前面的 catch-all 拦截或 CDN 缓存旧响应。本文按命中率拆 5 类,并给 curl -I 验证步骤。

你在 vercel.json / _redirects / netlify.toml 里写了一条 /old-path/new-path 的 301,部署完去浏览器里访问,结果 404 或者继续显示旧页面——这是 web 部署里最常见的”配置看起来对但就是没跑”问题。多数情况是因为规则文件没被 build 收录、规则顺序被前面的 catch-all 拦截、或者 CDN / 浏览器还在缓存旧响应。

本文按命中率拆开 5 类原因,并给出可以直接 curl -I 验证的修复路径。

常见原因

按命中率从高到低:

1. 规则文件不在 build 产物里

vercel.json 必须在仓库根目录;Netlify 的 _redirects 必须在 public/ 里(Astro / Vite 系),构建后会被复制到 dist/_redirects;Cloudflare Pages 同样要求 _redirects 出现在最终发布目录里。如果你把文件放在了 src/ 或者 app/,构建器不会自动复制过去。

如何判断:本地跑 npm run build 后看 ls dist/ls .vercel/output/,确认 _redirectsconfig.json 真的在那。Vercel 还可以在 Deployments → 选一次部署 → “Source” 标签里直接搜文件。

2. 顺序问题——前面的 catch-all 抢先匹配

重定向引擎大多按”自上而下,第一个匹配胜出”。如果你在 vercel.json 里先写了:

{
  "redirects": [
    { "source": "/:path*", "destination": "/new-site/:path*", "permanent": true },
    { "source": "/old", "destination": "/new", "permanent": true }
  ]
}

第二条永远跑不到,因为第一条已经吞掉所有路径。

如何判断:把 /old 单独提到列表第一条试一次,如果立刻 work,就是顺序问题。

3. CDN / 浏览器缓存旧响应

如果上一次部署没设置重定向、且 CDN 给那条 URL 缓存了 200 + HTML,新规则不会立刻生效。Cloudflare 默认对 HTML 有 4 小时 edge cache,Vercel Edge 也会缓存 redirect 响应本身。

如何判断curl -I "https://yourdomain.com/old-path?cb=$(date +%s)" 加 cache buster 看响应头。如果带 buster 是 301 但不带是 200,就是缓存问题。

4. trailing slash 不匹配

很多平台对 /old/old/ 视为两条不同 URL。Vercel 默认 trailingSlash: false,规则写 /old/ 永远不会匹配用户访问的 /old。Netlify _redirects 同理。

如何判断:把规则源里的斜杠和你站点实际 URL 风格对齐再测一次。

5. 用了 rewrites 但期望的是 redirects

rewrites 是 URL 不变、内部代理到另一个路径,用户地址栏看到的还是 /oldredirects 才会让浏览器跳到 /new 并返回 301 / 302。两个字段写错对方就会得到”看起来跳了又没跳”的怪现象。

如何判断curl -I 看返回码——200 是 rewrite,301/302/308 才是 redirect。

最短修复路径

按收益从高到低,前 3 步通常就能解决 80% 的问题。

Step 1:用 curl 看真实响应

不要只看浏览器地址栏,浏览器会自动跟随跳转、还可能命中本地缓存。直接 curl:

curl -I -L "https://yourdomain.com/old-path?cb=$(date +%s)"

读输出:

  • 第一个响应是 301 Moved Permanently + location: /new-path → 重定向生效
  • 第一个响应是 200 → 规则没跑(看 Step 2)
  • 第一个响应是 308 且 location 指向自己加了斜杠 → 是平台的 trailing slash 行为,不是你的规则
  • cf-cache-status: HIT / x-vercel-cache: HIT → 缓存问题(看 Step 3)

Step 2:确认规则文件在产物里、顺序正确

本地构建并检查:

npm run build
ls -la dist/ | grep -E '_redirects|vercel.json'
cat dist/_redirects 2>/dev/null || cat vercel.json

如果文件不在 dist/,按平台规范放正确位置:

平台规则文件必须放在
Vercelvercel.json仓库根目录
Netlify_redirectsnetlify.tomlpublic/ 或根目录
Cloudflare Pages_redirects发布目录(通常 public/dist/
Astro 静态_redirectspublic/_redirects

然后把具体规则放到通用规则之前。Vercel 示例:

{
  "redirects": [
    { "source": "/old", "destination": "/new", "permanent": true },
    { "source": "/blog/:slug", "destination": "/articles/:slug", "permanent": true },
    { "source": "/legacy/:path*", "destination": "/", "permanent": false }
  ]
}

Step 3:清 CDN,针对单个 URL

不要一上来就 purge everything——成本高、命中率低。针对那条具体 URL 清:

  • Cloudflare:Caching → Configuration → Purge Custom URLs,粘贴完整 URL(含 https://
  • Vercel:重新部署,或对该项目跑 vercel --prod --force
  • Netlify:Deploys → 选最新部署 → Trigger deploy → Clear cache and deploy site

清完再用 Step 1 的 curl + cache buster 验证。

Step 4:加 CI 冒烟测试

把重定向写完后,最容易的回归保护是在部署 hook 后跑几条 curl,断言响应码和 location。例:

#!/usr/bin/env bash
set -e
BASE="https://yourdomain.com"
check() {
  local from="$1" expected="$2"
  local actual=$(curl -s -o /dev/null -w "%{http_code} %{redirect_url}" "$BASE$from")
  if [[ "$actual" != "$expected"* ]]; then
    echo "FAIL $from → got '$actual', expected '$expected'"; exit 1
  fi
}
check "/old" "301 ${BASE}/new"
check "/blog/hello" "301 ${BASE}/articles/hello"
echo "All redirects OK"

在 GitHub Actions 的 deployment_status 事件里跑这段即可。

预防建议

  • 写完重定向立刻 curl -I 验证,不要靠浏览器看
  • 规则放序:具体在前、catch-all 在后;用注释标好
  • 部署后跑 5-10 条关键 URL 的冒烟测试,挂 CI
  • 统一站点的 trailing slash 策略,规则源里和实际一致
  • 改大批量重定向前先 git commit,便于按文件 diff 回滚

相关阅读

标签: #部署 / 托管 #排查 #排查