Meta robots vs X-Robots-Tag——谁说了算

`<meta name="robots">` 与 `X-Robots-Tag` HTTP header 冲突时,Google 会合并两者并取最严格的值。怎么用 curl 排查、怎么让两边保持一致。

**最快的修复:**对出问题的 URL 跑 curl -sI https://yourdomain.com/path | grep -i x-robots。如果有 X-Robots-Tag: noindex 这个 header,就是它在压制你的 meta——Google 在两个信号里取最严格的值,所以 header 的 noindex 每次都胜过 meta 的 index。在注入它的那一层把这个多余的 header 去掉,再触发重抓。如果根本没有 header,直接跳到情况 5(robots.txt 把页面挡住了)。

你在 staging 页上加了 <meta name="robots" content="noindex">,一周后发现生产环境的同一页从 Google 消失了。或者反过来:你在感谢页 meta 里写了 noindex,但它还在搜索结果里。两种情况的根源都是同一个——Google 通过两条通道读取 robots 指令,两条不必一致。HTTP 响应头 X-Robots-Tag 是另一条通道;当两者不一致时,Google 不会选一个作权威。正如 Google 的 robots meta tag 规范所写:“In the case of conflicting robots rules, the more restrictive rule applies.”(冲突时取更严格的规则)。也就是说它合并两条通道,逐项取最严格的值。

换句话说,CDN 规则里一条不起眼的 X-Robots-Tag: noindex、一个过期的 Vercel 部署、或者一段源站中间件,都可以静默杀掉一个 meta 里写着 index 的 HTML 页面。View Source 看不出问题——浏览器不给你看响应头。只有 curl -I(或 Network 面板,或 Search Console 的”HTTP 响应”视图)能看到真相。

先判断你属于哪一类

先跑下面两条检查,再对照命中的那一行:

Meta(curl -s + grep robotsHeader(curl -sI + grep x-robotsrobots.txt你的情况
noindex允许情况 1——还没重抓,或 meta 是 JS 设的
index / 无noindex允许情况 2——某个 header 在泄漏 noindex
不适用(PDF、图片、ZIP)noindex允许情况 3——非 HTML 资源被全局加了 header
每次抓取都在变每次抓取都在变允许情况 4——JS 在加载后改了 meta
noindexnoindexDisallow情况 5——robots.txt 把抓取整个挡住了

最后一列告诉你该用哪种修复。绝大多数”我设了 noindex 却没生效”(或”我没设 noindex,页面却消失了”)的反馈都落在情况 2 和情况 5。

先判断你属于哪种情况

情况 1:meta 里有 noindex,但页面仍被收录

怎么发现:

curl -s https://yourdomain.com/path | grep -i 'name="robots"'
# 输出:<meta name="robots" content="noindex">

curl -sI https://yourdomain.com/path | grep -i x-robots
# 输出:(什么都没有)—— meta 说 noindex,header 什么也没说

然后 Search Console 的 URL 检查里这一页显示已被收录。

原因:自从你加 meta 之后 Google 还没有重新抓取,或者 meta 是用 JavaScript 渲染的、本次抓取里 Googlebot 没执行 JS。header 是每次请求都会读到的;meta 要等重抓 + 渲染才能生效。

修复:把 noindex 渲染进 SSR 的初始 HTML(不要靠客户端 JS),然后用 URL 检查里的”测试实际网址”确认 Google 在渲染 HTML 里看到了 noindex,再点”申请编入索引”触发重抓。完整移除时间线见 设了 noindex 但页面还在搜索结果里

情况 2:meta 没有 noindex,但页面却从索引消失

怎么发现:

curl -sI https://yourdomain.com/path | grep -i x-robots
# X-Robots-Tag: noindex, nofollow

浏览器打开页面、查看源代码——没有 robots meta,或者写的是 index, follow。罪魁就是这个 header。

原因:典型泄漏路径:

  • 过期的 Vercel 部署。Vercel 会自动给系统生成的 *.vercel.app URL,以及没有绑定到 Production Domain 的部署——包括你推新版本后那些过期的生产部署——发送 X-Robots-Tag: noindex(见 Vercel 关于预览是否被收录的 KB)。如果 Google 之前收录过某个裸部署 URL,现在它读到的就是 noindex。绑定到非生产分支的自定义域不会带这个 header,所以这个坑通常是某个裸 *.vercel.app URL 漏进了索引。
  • **host 模式过度匹配。**WAF 或 CDN 规则原本只想覆盖 *.staging.example.com,但 host 模式同时匹配到了你的主域名。
  • **生产没设 env 变量。**源站中间件根据 NODE_ENV !== "production" 加上 X-Robots-Tag: noindex,结果生产环境其实没设 NODE_ENV,这个判断永远为真,规则照样生效。

修复:定位哪一层注入了 header。一层一层往回找:

# 直连源站(绕过 CDN)
curl -sI --resolve yourdomain.com:443:ORIGIN_IP https://yourdomain.com/path | grep -i x-robots
# 再走 CDN
curl -sI https://yourdomain.com/path | grep -i x-robots

源站干净、CDN 脏 → CDN 规则;两者都脏 → 源站/应用层。

情况 3:PDF、图片或其他非 HTML 资源从搜索消失

怎么发现:

curl -sI https://yourdomain.com/whitepaper.pdf | grep -i x-robots

PDF、图片、其他非 HTML 响应无法承载 <meta> 标签。对它们而言,Google 唯一能读到的 robots 信号就是 X-Robots-Tag。如果托管平台或框架把 X-Robots-Tag: noindex 作为静态资源的默认值,你的 PDF 永远无法被收录。

原因:某些框架(Next.js 的默认 header、S3+CloudFront 配了默认 noindex 策略)会给所有响应、包括静态资源都加 X-Robots-Tag

修复:把 noindex 规则限定到 HTML 页面或具体路径,而不是全站默认。

情况 4:表现飘忽——有时被收录有时没被

怎么发现:URL 检查显示渲染后的 HTML 带 noindex,但原始响应(“HTTP 响应”那一栏)没有。或者反过来。

原因:JavaScript 在页面加载后修改 meta。本次抓取 Googlebot 执行了 JS 就看到 noindex;没执行就看到原始的 index 值。header 通道始终被读取;meta 通道依赖渲染。

修复:不要用客户端 JS 切换 robots meta。在 SSR HTML 里就把最终值渲染出来,或者用服务端的 X-Robots-Tag

情况 5:两边都写了 noindex,页面还在被收录

怎么发现:

# 两个信号都在
curl -s https://yourdomain.com/path | grep -i robots  # meta 写了 noindex
curl -sI https://yourdomain.com/path | grep -i x-robots  # X-Robots-Tag: noindex

# 但是:
curl -s https://yourdomain.com/robots.txt | grep -i path
# Disallow: /path

原因:robots.txt Disallow 把抓取整个挡住了。Google 永远不去取这一页,永远看不到 meta 或 header,仅凭外链信号继续把 URL 留在索引里(就是那个著名的”虽被 robots.txt 屏蔽,但仍被收录”状态)。这是 noindex 失败最常见的形态。

修复:删掉 Disallow 那一行。URL 必须可抓取,Google 才能看到 noindex 并完成移除。

Google 怎么解决冲突

按 Google 文档(robots meta tag 规范):

  • 凡是能写进 robots meta 的规则,都能用 X-Robots-Tag header 发送——它们是同一套词汇、两条通道,都是有效信号。
  • 两条通道同时存在时,“the more restrictive rule applies”(取更严格的规则)——按指令逐项判断,而不是按来源整体取舍。
  • noindexindexnofollowfollow。摘要类控制里,nosnippet 胜过任何 max-snippet:[number],更小的 max-snippet 胜过更大的。
  • 如果 robots.txt 屏蔽了 URL,两个信号都读不到:“any information about indexing or serving rules will not be found and will therefore be ignored.”(关于索引或展示的规则都不会被发现,因此被忽略)。URL 还可能以无摘要的形式留在结果里。

一条过时建议要纠正:noarchivenocache 已不再是受支持的索引规则——别再用了。所以就有意义的规则而言,meta 和 header 之间不存在”谁赢”,是合并。实操原则:生产环境里绝不让它们打架。(完整指令清单和原文措辞见 Google 的 robots meta tag 规范。)

最短修复路径

按命中率排序:

  1. 对问题 URL 跑 curl -I → 60% 的情况下 header 就是凶手。View Source 里的 meta 会让你忽视真正原因。
  2. robots.txt → 25% 的情况下是 Disallow 让 Google 永远看不到你设的信号。
  3. 逐层排查请求链路 → 源站 header 正确但边缘错了,就是 CDN / WAF / 托管平台的规则。
  4. 把 robots meta 写到 SSR,别靠 JS → 剩下的边缘情况几乎都是”JS 设了 meta 但 Googlebot 这次没跑 JS”。

一个安全的生产配置

按内容类型分工。下面这套覆盖绝大多数站:

  • HTML 页面:robots meta 写进 SSR HTML。HTML 响应不要再加 X-Robots-Tag,除非有特殊理由。
  • PDF、图片、下载文件:用 X-Robots-Tag header(meta 用不了)。
  • Staging 域名X-Robots-Tag: noindex 严格限定在 staging 主机名上。

示例:把 staging 的 noindex 限定到主机名,而不是全局。

# Nginx——只在 staging
server {
  server_name staging.yourdomain.com;
  add_header X-Robots-Tag "noindex, nofollow" always;
}

server {
  server_name yourdomain.com;
  # 这里不加 X-Robots-Tag。每页的 noindex 走 meta。
}

怎么确认已经修好

别信 View Source——要在 header 层和 Google 自己的视图里确认:

  1. curl -sI https://yourdomain.com/path | grep -i x-robots 返回空(或你期望的值)。CDN URL 和源站都要各跑一遍,因为边缘可能会改写 header。
  2. 在 Search Console 里对该 URL 跑 URL 检查 → 点 测试实际网址。展开 HTTP 响应 一栏,确认没有意外的 x-robots-tag。“是否允许编入索引?“那一行应显示
  3. 申请编入索引 排队一次新抓取。注意配额:截至 2026 年 6 月,Search Console 每个资源每天大约只允许 10–12 次手动提交,一旦用满,按钮会灰掉 24 小时。少数几个重要页面这样做没问题;要做全站修复,就靠正常抓取周期,并重新提交 sitemap。
  4. 等重抓。首页或高流量页通常 24–72 小时内重新收录;深层、低流量的页面可能要一周以上。

预防建议

  • 按文件类型记录哪个信号 canonical,写进仓库里的 SEO README。
  • CI 加一条检查:对生产 URL 抽样跑 curl -I,意外出现 X-Robots-Tag 就让构建失败。
  • 同一 URL 绝不同时 meta noindex + robots.txt Disallow——Google 永远看不到 noindex。
  • 不要用客户端 JS 切换 robots meta——Googlebot 本次可能跳过 JS。
  • 改完 Vercel / Netlify / Cloudflare 设置后,立刻对若干页面跑 curl -I 验证 header 没有意外变化。

FAQ

Q:meta 里设了 noindex 但页面还在 Google。先查什么? A:先跑 curl -sI https://yourdomain.com/path | grep -i x-robots。如果没 header,再看 robots.txt 是不是对这个路径写了 Disallow。这两步能解决约 85% 的情况。都不是的话,几乎都是”Google 还没重抓”——等,或者用 URL 检查里的”申请编入索引”。

Q:同一页能同时用 meta 和 X-Robots-Tag 吗? A:能,但必须一致。Google 按最严格的指令合并。meta 写 index、header 写 noindex,最终结果是 noindex。HTML 上同时写两个是冗余,选一个就好。

Q:X-Robots-Tag 能用在 HTML 上吗? A:能,效果和 meta 完全一致。只是不那么常见,因为大多数 CMS 会替你写 meta。X-Robots-Tag 是非 HTML(PDF、图片、ZIP)等无处写 meta 的场景的正确选择。

Q:staging 把 noindex 泄漏到了生产,怎么最快撤销? A:先把 header 移掉(部署或改 CDN 规则),再去 Search Console:对受影响 URL 做 URL 检查 → “测试实际网址” → 在 HTTP 响应视图里确认 header 已消失 → “申请编入索引”。首页或顶层页通常 24–72 小时就能重新收录。记得每天约 10–12 次提交的上限,优先提交最重要的 URL。

Q:我站点的某个 something.vercel.app URL 在 Google 里,但真正的域名却不在,为什么? A:Vercel 默认会给系统生成的 *.vercel.app URL 加 X-Robots-Tag: noindex,所以这些本不该被收录——但如果 Google 在读到 header 之前就通过某个链接发现了它,那个裸 URL 可能会残留。修法是把 canonical 和站内链接都指向你的生产域名,确认生产域名不返回 noindex header(curl -sI),再让那个 *.vercel.app URL 自然掉出索引。永远不要把裸部署 URL 当作 canonical 来对外提供真实内容。

Q:nosnippetmax-snippet 在 meta 和 header 之间的合并规则一样吗? A:一样——同样取最严格的值。meta 写 max-snippet:50、header 写 max-snippet:200,最终生效的是 50。而 nosnippet 会盖过任意一条通道上的任何 max-snippet 值。

相关阅读

标签: #SEO #排查 #排查 #结构化数据 #robots.txt #X-Robots-Tag