本文约 3,082 字,预计阅读 8 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:半年前做的一次跳转,排名一直没接上
有个站改版时把旧域名用 302 跳转到新域名,理由是”先试试,稳定了再改 301″。半年后新域名的排名始终上不去,旧域名的 URL 依然大量出现在搜索结果里。原因就出在这个 302 上——它告诉搜索引擎”这只是临时搬家”,于是旧 URL 一直被保留在索引中,权重不向新地址传递。
重定向状态码的选择不是技术细节,而是直接写进索引策略的指令。更麻烦的是,很多框架和插件的默认值并不是你想要的那一个:PHP 的 header('Location: ...') 默认发 302,WordPress 的 wp_redirect() 默认也是 302。如果不显式指定,你以为做了永久跳转,实际发出去的是临时跳转。
原理分析:四种状态码的语义与引擎处理
| 状态码 | 语义 | 是否传递信号 | 是否改变请求方法 | 典型场景 |
|---|---|---|---|---|
| 301 | 永久移动 | 是 | 可能变为 GET | 域名迁移、URL 结构变更 |
| 302 | 临时移动 | 否 | 可能变为 GET | A/B 测试、临时活动页 |
| 307 | 临时重定向 | 否 | 严格保持原方法 | 临时跳转且需保留 POST |
| 308 | 永久重定向 | 是 | 严格保持原方法 | API 永久迁移、保留 POST |
关键点有两个:
一是信号传递。 Google 明确说明 301 会把排名信号传递给目标页;302 被长期使用时,Google 会”猜测”你可能想表达永久意图,但这种猜测不可靠、也不及时。所以永久迁移必须显式用 301(或 308)。
二是请求方法保持。 301/302 在历史上的实现允许把 POST 改成 GET,导致表单数据丢失;307/308 是后来新增的严格版本,强制保持原请求方法。对普通页面跳转影响不大,但对 API 接口和表单提交至关重要。
需要注意的另一类”假跳转”:用 <meta http-equiv="refresh"> 或 JS 的 window.location 实现的跳转。Google 会处理 meta refresh,但把它当作 301 还是 302 取决于延迟时间(0 秒近似永久,大于几秒按临时处理),可靠性远低于服务端跳转,能不用就不用。
保姆级实操
步骤 1:确认现有跳转的真实状态码。 浏览器会自动跟随跳转,看不出来,必须用命令行。
# 打印每一跳的状态码与 Location
curl -sIL https://old.example.com/page | grep -Ei "^(HTTP/|location:)"
步骤 2:永久迁移统一改成 301。 PHP 层用 wp_redirect() 时必须显式传第二个参数。
<?php
// WordPress 中正确的永久跳转写法(默认 302,必须显式指定 301)
add_action('template_redirect', function () {
if (is_page('old-landing')) {
wp_redirect(home_url('/new-landing/'), 301);
exit;
}
});
// 原生 PHP 写法:第三个参数才决定状态码,默认 302
header('Location: https://example.com/new/', true, 301);
exit;
wp_safe_redirect() 与 wp_redirect() 参数一致,但前者会校验目标域名是否在允许列表内(通过 allowed_redirect_hosts 过滤器控制),跳转外部域名时更安全。
步骤 3:服务器层优先于 PHP 层。 能用 Nginx/Apache 处理就不要进 PHP,省一次完整的应用启动开销,对 TTFB 也有帮助。
# Nginx:域名整体迁移
server {
listen 80;
server_name old.example.com;
return 301 https://new.example.com$request_uri;
}
# 单页跳转,注意 301 与 302 的区别
location = /promo {
return 302 https://example.com/campaign/2026-spring;
}
Apache 的写法:
# .htaccess
Redirect 301 /old-page https://example.com/new-page
RewriteRule ^blog/(.*)$ https://example.com/news/$1 [R=301,L]
步骤 4:需要保留 POST 方法时用 307/308。 站点的表单、Webhook 接收端临时迁移用 307,永久迁移用 308。
# 表单提交端点永久迁移,保留 POST 方法
location = /api/subscribe {
return 308 https://api.example.com/subscribe;
}
步骤 5:清理跳转链。 一次跳转最理想,超过两次就开始损失信号且拖慢速度。用 Screaming Frog 的 “Redirect Chains” 报告或 httpstatus.io 检测,把 A→B→C 直接改成 A→C。
步骤 6:验证。 改完后用 curl -sIL 确认状态码与跳转次数,并在 GSC”网址检查”里看 Google 读到的最终地址。批量跳转的规划方法可参考 批量 301 重定向规划。
常见踩坑
- 案例: 某站 HTTP 跳 HTTPS 用了 302,且
http://版本带 www、https://版本不带 www,形成http://www → https://www → https://的三跳链。改成一个return 301 https://example.com$request_uri;后,LCP 相关的重定向耗时从 1.1 秒降到 0,索引中的重复 URL 也消失了。 - 用
wp_redirect()不传第二个参数,默认 302,以为做了永久跳转实则没有。 - 用 JS 的
window.location.href做跳转,爬虫执行 JS 有延迟且可能不执行,索引更新极慢。 - 302 长期使用后改 301,信号传递需要数周才能体现,期间排名波动属正常,别反复改回去。
- 表单接口用 301 跳转,POST 被降级成 GET,参数全部丢失,用户提交失败。
- 把 302 当作”临时下线”手段,结果页面被判定为软 404 或重复内容,需要处理时可参考 404 与软 404 处理。
- 重定向目标自身又是一次重定向,形成循环,浏览器报
ERR_TOO_MANY_REDIRECTS。上线前必须逐条curl验证。
总结
选择标准一句话概括:永久变更用 301(需保留请求方法时用 308),临时变更用 302(需保留请求方法时用 307),其他实现方式(JS、meta refresh)都不作首选。行动清单:用 curl -sIL 盘点现有跳转状态码;把永久迁移全部改成 301;把多跳链压平成一跳;表单与 API 端点改用 307/308;上线后逐条验证。跳转看着简单,却是改版事故的第一高发区,值得花半天时间逐条核对。
