hreflang 写错不会弹提示,只会让 Google 长期给错的语言版本

hreflang 写错不会有任何报错。它不是一份会被校验的配置,而是一组手写在页面 head、HTTP 响应头或 sitemap 里的双向声明:Google 读到什么就按什么分发,读到缺了自引用的、缺了返回标签的、指向不存在 URL 的,直接把那一条丢掉,不通知任何人。所以正确的做法是把每一组 hreflang 当成一张必须闭合的对照表来维护——每个页面在表里都要列出自己,并且和每一个对应页互相指回;而真正能被 Search Console 抓到的错只有两类,缺少返回标签和无法识别的语言代码,其余大量错误(合法但选错国家的区域代码、canonical 与 hreflang 打架、多路径声明不一致)在任何报告里都不会出现,只会表现为 Google 长期把错的语言版本发给错的用户。

我上一次被这件事咬到,是在一个德语站上。那天晚上十一点多,我在查一批产品页在德国的曝光为什么一直上不去,打开 Search Console,覆盖率报告干干净净,一个红字都没有。后来我做了件很笨的事:打开页面源码,Ctrl+F 搜 hreflang,把那五行 link 一行一行抄进记事本。抄到第三行的时候我看出来了——这一组里没有它自己。德语页列出了英语、法语、西班牙语、日语,唯独没列出 de-DE 那个自己。

那段代码在浏览器里看起来完全正常。没有警告,没有黄色感叹号,也没有任何通知告诉过我它被丢掉了。

一组 hreflang 是一张必须闭合的对照表

我把 hreflang 理解成一张表,而不是一条条孤立的标签。表的每一行是“A 认为 B 是它的某种语言版本”。这张表有两条硬规矩,违反任何一条,那一行就作废,而作废是静默的。

第一条,每个页面必须在自己的表里出现一次。上面那个德语页没写自己,Google 处理这一组时就不认为它属于这个语言集群,于是那五条标签一起被丢掉。你从外面看不出任何区别:页面照样被索引,照样有排名,只是永远进不了“德语用户看到德语版本”的分发逻辑里。

第二条,指向必须是双向的。A 写了 B,B 就必须在自己的 head 里写回 A。只写单向的,Google 会把这一对拆掉,两边都按没有声明处理。这一条最容易在“加语言”的时候破:新上一个巴西葡语页,你在所有老页面的 head 里加了 pt-BR 那一行,却忘了新页面自己的 head 里要把另外五个语言全列一遍。老页面全对,新页面全废,而新页面恰恰是你最想推的那个。

语言代码和区域代码是两个旋钮

写 de 和写 de-DE 不是一回事。只写语言代码,等于告诉 Google“这一页不针对某个具体国家”;写成 de-DE,是“这一页给德国”。前者适合那种面向所有德语读者的内容,后者适合价格、货币、配送时效、法规条款绑定了国别的内容。选错了不报错,只会让 Google 在该选你的时候不选你。

几个我逐条核对过的点:

  • en-UK 不存在,英国是 en-GB。写 en-UK 属于无法识别的语言代码,这个反而会被报告抓出来。
  • 中文别想当然。zh-CN 是给中国大陆的简体,zh-Hans 是“简体中文,不限地区”。要面向新加坡华人,zh-SG 和 zh-Hans 是两码事。
  • 西班牙语分 es-ES 和 es-419(拉美)。给整个西语市场做一套页面,es-419 往往比 es-ES 更贴,但你得认这个代码。
  • 区域代码必须是 ISO 3166-1 的两位。de-AT 是奥地利,合法;到底写 de-DE 还是 de-AT,取决于你的德语页里写的是不是奥地利的价格和配送。

报告里真正能看到的,只有两类

Search Console 里那个 International Targeting(国际定位)报告,现在被收在“旧版工具和报告”底下,入口一年比一年深。它确实会告诉你一些事,但只告诉两件事。

一是缺少返回标签(lacking return tags)。它按页面列出来,告诉你这一页指了谁而谁没指回来。这个很有用,但它给的是线索不是答案,你还得自己打开那些被指向的页面去确认。

二是无法识别的语言代码。拼错的、不存在的、格式不对的,会在这里报。

就这两类。我把它当成体检里的两项血指标:能查出两种病,其余的全查不出来。

剩下那些错,不会出现在任何地方

这才是我真正想说的部分。下面这几种,你把 Search Console 翻遍也找不到一行字,它们照样决定 Google 给谁看哪个版本。

合法但错误的区域代码。把面向德国全国的页面写成 de-AT,代码合法,Google 照单全收,报告里一个字都没有。结果是奥地利用户被优先匹配到它,德国用户匹配不到。这类错最难发现,因为它不违反任何规则,只是和你的意图不一致。我现在的做法是在核对表里多加一列“意图地区”,和实际写进去的代码并排放着看。

自引用缺失。前面那个德语页就是。整组作废,报告里通常什么都不出现。

canonical 与 hreflang 打架。如果页面 A 的 canonical 指向了 B,那 A 上的 hreflang 会被忽略,因为 Google 认为 A 不是独立页面,它的语言声明没有意义。多语言站里这种结构性问题特别多,尤其是分页、带参数的列表页,以及“语言版本只是同一页的翻译”这种情形。

多路径声明不一致。hreflang 可以放在 head、HTTP 头里,也可以放在 sitemap 的 xhtml:link 里。用哪几种都行,但这几种说的必须是同一套 URL 和同一套映射。head 里说 A 对应 B,sitemap 里说 A 对应 C,Google 只会挑一套用,另一套丢掉,而哪套被选中你控制不了。这也是为什么我倾向只留一条路径,通常是 head 加自引用,把 sitemap 那套多余的清干净,减少自己跟自己打架的机会。

URL 不完全一致。hreflang 里写的是绝对地址,http 与 https、有没有 www、结尾有没有斜杠,只要和实际页面或 canonical 差一个字符,这条指向就落空了。落空不报错,它只是指向了一个并不会声明回来的地方。

x-default 是那句“以上都不匹配”

如果你的英语页是兜底页,所有没被列出来的语言用户都看它,那你要在每一组里加一条 x-default 指向它。不加不会报错,只是当用户的语言不在列表里时,Google 得自己猜一个,猜的结果未必是你想给的那个。有些站把 x-default 指向语言选择页,这也行,取决于你想让用户先做选择还是先看到内容。

我现在的检查顺序

不用工具,一张表就够。你可以跟着我做一遍。

第一步,把某一组里每一个页面的 head 中的 alternate hreflang 全部抄出来,做成两列:声明方、被指向方。抄的时候连 href 一起抄全,一个字都别省。

第二步,数条数。假设有五个语言版本、x-default 也设了,那每个页面的 head 里应该有六条,其中一条的 href 就是它自己。少一条就是漏了。

第三步,反向查。表里每一行“X 指向 Y”,你都要打开 Y,确认 Y 的表里有“Y 指向 X”。这一步最花时间,但缺返回标签、自引用缺失、URL 写错,全靠它抓。

第四步,拿语言代码去对 Google 支持的语言和地区列表,能抓出 en-UK 这一类。

第五步,看 canonical。页面自己的 canonical 是不是指向自己(或指向该指的那一版),和 hreflang 里的自引用是不是同一个 URL。

第六步,如果 sitemap 里还有 xhtml:link,回到第二步重做一遍,两套结果必须一模一样。

一个五语言的站,全套加起来几十行,半小时能过完。比起三个月后发现德语流量一直不对再回头查,这半小时很值。

它是提示,不是指令

最后说一句限制。hreflang 对 Google 是提示,不是命令。就算你写得完全正确,如果两个语言版本的内容几乎一样,比如只把产品名翻译了、正文全是同一段模板,Google 可能只留一个版本在索引里。hreflang 解决的是“该给谁看哪一版”,解决不了“这两版值不值得同时存在”。内容真的按语言写过,标签才有意义。另外它本身不改变用户访问的 URL,也不做跳转:用户从搜索结果点进来落在哪一版,是 Google 根据这些声明决定的。

那张表我现在是常备的。每加一个语言、每改一次 URL 结构、每换一次域名,都回到表上重抄一遍。hreflang 的问题从来不是写不出来,而是写出来了、看起来对、然后三年没人再看它一眼。

本文仅为教育性信息,不构成专业意见;在依据文中内容采取任何行动前,请先咨询相关领域的专业人士。