2026-10-03 更新:本文改用 RFC 9110/9111/3986/6797、Google Search Central 官方文档与 sitemaps.org 协议原文重写,删去原先的本机测试内容;所有语义结论都标注了规范条款或官方文档出处。
改版时最容易埋雷的不是页面本身,而是 URL:换域名、换协议、加 www、目录改名、参数调整——每一步都可能产生"同一份内容有多个地址"的局面。搜索引擎处理这种情况的依据是明确的规范与官方文档,把规则先搞清楚,再动手写配置。
一、先把"唯一形态"定死,再写第一行代码
在动手之前需要定一个"规范形态"(canonical form),其余形态全部指向它。规范的判断依据来自 RFC 3986 与 Google 官方文档:
- 协议与主机名大小写不敏感,规范形式是小写。RFC 3986 §3.1 规定 scheme 名大小写不敏感、规范形式为小写,实现应把小写作为唯一输出形式;§3.2.2 规定 host 子组件(IP 字面量或注册名)大小写不敏感。
- 默认端口要从 URL 里省掉。RFC 3986 §3.2.3 说明:当端口为空或等于该 scheme 的默认端口(http 为 80)时,生产者和归一化器应省略端口及冒号。
- 空路径等价于
/。RFC 3986 §6.2.3 明确对 http scheme 而言http://example.com、http://example.com/、http://example.com:/、http://example.com:80/四者等价。 - 路径默认大小写敏感,只有 scheme 与 host 归一为小写(RFC 3986 §6.2.2.1),所以不要把"路径大小写不敏感"当成前提。
- 百分号编码要归一。RFC 3986 §6.2.2.2 要求把编码后对应 unreserved 字符的百分号编码解码(例如
%7E与~等价),§6.2.2.3 要求用remove_dot_segments算法去掉路径中的.与..段。 - 站点必须在 www 与裸域之间选一个。sitemaps.org 协议要求:一个 Sitemap 里所有 URL 必须来自单一主机(
www.example.com或store.example.com,不能混用)。
⚠️ 有一条常见说法需要纠正:"//a 与 /a 是同一个页面"没有规范依据。RFC 3986 只规定了空路径等价 / 与点段移除,并没有声明重复斜杠等价。这种等价性属于具体服务器/CDN 的实现行为,不能当规范结论写进改版方案。
二、301 与 302 的差别,是"新地址是不是永久"
RFC 9110 §15.4.2 对 301 的定义是:目标资源已被分配新的永久 URI,未来对该资源的引用都应使用新 URI,服务器建议可永久替换旧引用;服务器 SHOULD 生成含首选 URI 的 Location 头。
RFC 9110 §15.4.3 对 302(Found)的定义是:目标资源临时位于另一个 URI,由于重定向可能变动,客户端未来的请求应继续使用原目标 URI。
差别不只是措辞。对搜索引擎来说,Google 的官方文档把 HTTP 301、308 归为永久重定向(搜索结果里展示新目标页),把 HTTP 302、303、307 归为临时重定向(搜索结果里展示来源页);并且明确指出永久重定向是"目标应成为 canonical"的强信号。同站点的另一份官方文档给出了信号强度排序:重定向为强信号、rel=canonical 注解为强信号、Sitemap 收录为弱信号,三者可以叠加使用;如果都不指定,Google 会自行判定最佳版本——并且强调这些"指示只是 hint,不是规则"。
三、状态码别乱用:方法语义才是分水岭
改版里真正让人栽跟头的是"跳转后方法变了"。RFC 9110 把这几种码的语义分得很清楚:
| 状态码 | 是否永久 | 方法变化 | 规范依据 |
|---|---|---|---|
| 301 Moved Permanently | 永久 | 历史原因下 UA MAY 把后续请求方法从 POST 改成 GET | RFC 9110 §15.4.2 及该节 Note |
| 302 Found | 临时 | 同上,可被降级为 GET | RFC 9110 §15.4.3 |
| 303 See Other | 临时 | 明确用于"用另一个 URI 提供间接响应",UA 可执行检索请求(GET/HEAD) | RFC 9110 §15.4.4 |
| 307 Temporary Redirect | 临时 | UA 自动跟随 MUST NOT 改变请求方法 | RFC 9110 §15.4.8 |
| 308 Permanent Redirect | 永久 | 语义同 301,但不改变请求方法 | RFC 9110 §15.4.9 |
结论很直接:表单 POST 后的跳转、API 的临时跳转,如果不想让方法被改成 GET、不想丢掉请求体,就别用 301/302,用 307/308。反过来说,把 POST 结果的落地页写成 303 是最贴合规范的用法。
四、Location 的写法:相对与绝对都合法,fragment 有继承规则
- RFC 9110 §10.2.2 规定
Location头由单个 URI-reference 构成:写成相对引用时,最终值由它与目标 URI 解析计算得出。所以相对地址和绝对地址都是合法写法。 - 同一节还规定:如果 3xx 响应的
Location值不含 fragment 组件,UA 必须把它视作继承原请求 URI 引用的 fragment。规范的示例是:对http://www.example.org/index.html#larry返回Location: http://www.example.net/index.html,UA 最终应访问http://www.example.net/index.html#larry。
五、缓存:301 会被"记住",这是规范层面的结论
- RFC 9110 §15.4.2 说明 301 响应是 heuristically cacheable(启发式可缓存),除非方法定义或显式缓存控制另有说明。
- RFC 9111 §4.2.2 进一步说明:没有显式过期时间时,缓存可以分配启发式过期时间;这套启发式只适用于被定义为可缓存的响应(旧规范里的说法是 cacheable by default)。
- 如果只想临时发一个 301(比如灰度迁移、灰度站点还没准备好),RFC 9111 §5.2.2.5 定义的
no-store响应指令是官方给的工具:缓存不得存储请求或响应的任何部分,也不得用该响应满足其他请求。
⚠️ 需要说清楚的边界:规范没有规定 301 具体的缓存时长。规范层只到"heuristic cacheable",浏览器实现各自的期限属于实现细节,网上流传的"永久缓存"或"缓存 90 天"都不可当规范结论引用。真要撤回一个 301,最可靠的办法是让目标地址本身仍然可用(或者反过来,让旧地址返回 200 并给新地址 rel=canonical)。
关于"每一跳都是一次往返、所以要合并跳数"这类说法,同样缺少官方来源:Google 的文档只说明永久与临时重定向在索引上的差别,没有给出跳数与性能的量化结论。工程上少一跳当然更好,但请把它当作常识判断,别写成"官方规定了 N 跳上限"。
六、http → https 的 301 与 HSTS 的关系
RFC 6797 §7.2 规定:当 HSTS Host 通过非安全传输收到 HTTP 请求时,SHOULD 返回含表示永久重定向的状态码(例如 301)以及指向 https 的 Location 的响应——这是 SHOULD 而非 MUST。
另一侧的行为在 RFC 6797 §8.3:UA 在准备解引用任何 http URI(包括跟随 HTTP 重定向时)之前,MUST 先判断该 URI 的域名是否匹配已知的 HSTS Host。也就是说,已经访问过的浏览器会在本地把 http 升级为 https,那一跳对它根本不存在。
⚠️ HSTS 按主机生效:RFC 6797 §9 的示例指出,如果 HSTS 头只在 example.com 发出而页面链接都指向 www.example.com,部分 UA 不会把 example.com 记为 Known HSTS Host,www 的用户将不受保护。所以 www 与裸域要各自覆盖。
七、写法示意(语法以各服务器官方文档为准)
下面几段是结构示意,用来表达"要做出的响应形态",具体语法请以各项目的官方文档为准:
# Caddy:把非规范形态 301 到规范形态(示意)
example.com {
redir https://www.example.com{uri} permanent
}# nginx:升 https 并补 www(示意)
server {
listen 80;
server_name example.com www.example.com;
return 301 https://www.example.com$request_uri;
}# Apache:裸域 301 到 www(示意)
RewriteEngine On
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com$1 [R=301,L]三条共同点,比语法本身重要:
Location(或等价指令)要带上原始路径与查询串,写成固定字符串就等于把所有子页面都丢到首页;- 能一趟解决的别用两趟(http+裸域直接 301 到 https+www,而不是先补 www 再升 https);
- 改完必须亲自验证"旧地址 → 新地址"这一步真的返回 301 而不是 302/307,且
Location正确。
八、上线前检查清单
- [ ] 定好唯一形态(协议、www、路径大小写、尾斜杠、参数规范),并写进团队文档;
- [ ]
http://example.com→https://www.example.com一趟到位,返回 301; - [ ] 页面换址用 301;表单/API 的临时跳转用 307/308 或 303,别用 301/302;
- [ ]
Location保留原始路径与查询串; - [ ] www 与裸域都发 HSTS 头;
- [ ] 全站页面的 canonical 指向唯一形态(重定向 + canonical 是强信号,可叠加);
- [ ] Sitemap 里所有 URL 来自单一主机,
loc以协议开头、按服务器要求决定是否带尾斜杠; - [ ] 旧地址不是直接 404:长期存在的旧地址保留 301 映射表;
- [ ] 灰度期若要"可撤销的永久跳转",用
Cache-Control: no-store明确表态。
九、换成你的环境要改哪几处
| 本站用的 | 换成你的 | 始终不变的部分 |
|---|---|---|
| 具体 Web 服务器(Caddy / nginx / Apache) | 你的服务器与配置语法 | 响应必须是 301 且 Location 带原始路径与查询串 |
| 某个 CDN | 你的 CDN,注意其缓存规则对 301 的处理 | 301 属可缓存响应,灰度期要显式 no-store |
| 某套永久链接结构 | 你的 URL 结构 | 唯一形态、大小写与编码归一、默认端口省略 |
| 具体的定向映射表 | 你的旧地址清单 | 301 是永久信号,改错了要有回滚路径 |
参考来源
- RFC 9110, HTTP Semantics(301/302/303/307/308、Location、301 可缓存)— https://www.rfc-editor.org/rfc/rfc9110.html
- RFC 9111, HTTP Caching(启发式新鲜度、
no-store)— https://www.rfc-editor.org/rfc/rfc9111.txt - RFC 3986, URI Generic Syntax(大小写归一、默认端口、点段移除、等价性)— https://www.rfc-editor.org/rfc/rfc3986.txt
- RFC 6797, HTTP Strict Transport Security (HSTS)(§7.2、§8.3、§9 示例)— https://www.rfc-editor.org/rfc/rfc6797.txt
- RFC 5890, IDNA: Definitions and Document Framework(A-label / U-label)— https://www.rfc-editor.org/rfc/rfc5890.txt
- Unicode, UTS #46: Unicode IDNA Compatibility Processing — https://www.unicode.org/reports/tr46/
- Google Search Central, Redirects and Google Search(永久 vs 临时的索引差异)— https://developers.google.com/search/docs/crawling-indexing/301-redirects
- Google Search Central, How to specify a canonical URL(信号强度排序)— https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls
- Google Search Central, What is canonicalization(协议/参数等重复形态)— https://developers.google.com/search/docs/crawling-indexing/canonicalization
- Google Search Central, URL structure best practices(保留字符需百分号编码)— https://developers.google.com/search/docs/crawling-indexing/url-structure
- sitemaps.org, Sitemaps XML format(单一主机、
loc约束)— https://www.sitemaps.org/protocol.html