国际化 SEO 与 hreflang 实战
构建多语言、多地区站点,让它在每个市场都能排名——把 hreflang 做对。
当你的内容不再只服务于一类受众时,你就该考虑国际化 SEO 了。有两个触发点会逼你面对这个问题。第一个是语言:你把站点翻译成德语、日语或西班牙语,需要让每个译文在对应语言的读者面前出现。第二个是地区:语言相同,但市场不同——美国店铺和英国店铺都用英语,却有不同的价格、配送、拼写和法律文案。很多时候你会同时撞上两者:面向美国的英语、面向英国的英语、面向德国的德语、面向奥地利的德语。
搜索引擎并不会自动知道你的 /de/produkt 页面是 /en/product 的德语对应版本。一旦让它自己猜,它就会猜错,代价非常具体:
- 地区错配排名。 你的美元定价页面在英国搜索者面前排名,他们一到结账看到货币不对就跳出。
- 自我蚕食。 两个几乎相同的英语页面争夺同一个查询。Google 几乎是随机挑一个,把你的信号一分为二,结果两个页面都不如单页面排得好。
- 重复内容稀释。 各 locale 间未翻译的模板文案和共享模板看起来像单薄、重复的内容,拖累整个集群。
hreflang 就是修复这个问题的机制。它是一组注解,告诉搜索引擎:“这个 URL 是本页面的 X 语言、Y 地区版本,这里是它所有的兄弟版本。“做对了,它会把每个搜索者引导到为他们打造的页面。做错了——而且非常容易做错——它要么悄无声息地什么都不做,要么更糟,把爬虫指向错误的页面。本指南是实战派版本:先讲结构,再讲实现,最后讲那些会吞掉你好几个小时的故障模式。
URL 结构——hreflang 立足的地基
在做任何注解之前,你要先决定 locale 如何映射到 URL 上。这个决定很难回头(它会改变站点上的每一个 URL),所以要慎重权衡。有三种可行的方案。
| 方案 | 示例 | SEO 权重 | 给 Google 的地理信号 | 成本与搭建 | 维护 |
|---|---|---|---|---|---|
| ccTLD(国家代码顶级域) | example.de、example.co.uk | 分散——每个域从零开始积累权重 | 最强——.de 是毫不含糊的德国信号 | 高——购买/守护多个域名,独立托管与证书 | 高——每个域都是独立的 SEO 资产 |
| 子目录 | example.com/de/、example.com/en-gb/ | 集中——所有 locale 共享一个域的权重 | 中等——通过 hreflang + 内容设定 | 低——单域、单证书、单次部署 | 低——单代码库、单资产 |
| 子域 | de.example.com、uk.example.com | 多半分散——Google 把子域视为半独立 | 中等 | 中——每个子域单独 DNS + 证书 | 中 |
对大多数站点的建议:子目录。 它们让每个 locale 继承你主域已经积累的权重,而不是逼每个市场从零往上爬。它们运营成本最低——一个仓库、一条部署流水线、一张 TLS 证书、一份要监控的资产。而且它们正是静态站点生成器天然产出的形态。
把 ccTLD 留给地理信号值得付出成本的场景:你是有资源在每个国家独立建立权重的大品牌、本地信任极其重要(金融、医疗、与政府相关)、或者监管实际上要求一个本地域名。子域是折中选项,通常出于基础设施原因(每个地区独立技术栈)而非 SEO 原因——Google 表示它处理得不错,但实践中权重集中不如子目录干净。
💡 提示:locale 段要放在路径的最前面——
/de/blog/post,而不是/blog/de/post。顶级前缀含义明确、易于路由,并能让你在路由树的某一个分支上统一施加 locale 逻辑(货币、语言头)。
🧑💻 开发者视角:无论你选哪种,都要让 locale 在代码里成为单一事实来源——一个配置对象,把
locale -> { hreflang, currency, path prefix }映射起来。其他所有系统(sitemap、hreflang 标签、语言切换器、分析)都应从这一处读取。哪天你要加fr-CA,你想改的是一个文件,而不是七个。
hreflang 实现——三种交付方式
hreflang 是同一份数据,通过三种渠道之一交付。每个页面选一种主方式;在同一 URL 上混用会引来矛盾。下面一切之上、不可让步的铁律是:注解必须是双向的(return 标签)。 如果页面 A 把 B 列为它的德语备选,那么 B 也必须把 A 列为它的英语备选。哪怕只有一条链接缺了回指,Google 就会忽略该页面整个集群。把它当成握手——两只手都得伸出来,否则不成交。
方式一——HTML <link> 元素
最常见的方式。每个页面的 <head> 列出所有 locale 备选,包括它自己(自引用),再加 x-default。
<!-- In the <head> of https://example.com/en/product -->
<link rel="alternate" hreflang="en" href="https://example.com/en/product" />
<link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/product" />
<link rel="alternate" hreflang="de" href="https://example.com/de/produkt" />
<link rel="alternate" hreflang="zh-CN" href="https://example.com/zh-cn/product" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/product" />
这一组里的每个备选页面都必须携带相同的区块(把自己的 URL 作为其中一条自引用)。这种一致性本身就是双向握手。
方式二——XML sitemap
适合大型站点:你在一个文件里维护注解,而不是分散在成千上万个页面头里,单条 sitemap 条目就一次性声明整个集群。
<url>
<loc>https://example.com/en/product</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/product"/>
<xhtml:link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/product"/>
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/produkt"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/product"/>
</url>
记得在根元素上加命名空间:xmlns:xhtml="http://www.w3.org/1999/xhtml"。双向规则同样适用——集群里的每个 <url> 条目都需要完整的一组 xhtml:link 子元素,而不只是回指某一个对等页面。
方式三——HTTP Link 头
对于像 PDF 这类没有 <head> 可改的非 HTML 文件,这是唯一选项。以响应头形式交付。
Link: <https://example.com/en/manual.pdf>; rel="alternate"; hreflang="en",
<https://example.com/de/handbuch.pdf>; rel="alternate"; hreflang="de"
如何选方式
| 方式 | 最适合 | 要当心 |
|---|---|---|
HTML <link> | 中小型站点,完全掌控模板 | 页面体积;每次 locale 变动都要改每个页面 |
| XML sitemap | 大型站点,程序化生成 | sitemap 必须与线上 URL 完美同步 |
| HTTP 头 | PDF 及其他非 HTML 资源 | 需要服务器/边缘配置;容易忘 |
把代码写对
值是 language 或 language-REGION:
- 仅语言——
en、de、zh、fr。面向该语言在任何地方的使用者。 - 语言 + 地区——
en-GB、en-US、zh-CN、pt-BR。面向该语言在该国家的使用者。
语言代码是 ISO 639-1(两个字母)。地区代码是 ISO 3166-1 alpha-2(两个字母,一个国家,不是大洲或语言)。经典陷阱:英国的国家代码是 GB,不是 UK。而 x-default 是兜底桶——展示给那些其语言/地区你没有明确覆盖的所有人(下文细说)。大小写不强制,但惯例形式是语言小写、地区大写:zh-CN、en-GB。
常见错误——会吞掉好几个小时的故障模式
hreflang 是悄无声息地失败的。没有错误页;标签只是被忽略,你的各个 locale 在排名里慢慢漂移。下面是反复出现的成因。
-
缺少 return 标签。 页面 A 指向 B,但 B 不回指 A。这是最常见的单一故障——而且它使整个集群失效,而不只是那条断掉的链接。永远在每个页面渲染完整、对称的一组。
-
代码错误或无效。 用
en-UK而不是en-GB。臆造地区代码(en-EU——根本没有EU这个国家)。在该用地区的地方用了语言。一个无效条目就能让整组作废。对照 ISO 列表校验。 -
相对 URL。 hreflang 要求带协议和主机的绝对 URL——
https://example.com/de/produkt,绝不能是/de/produkt。相对 URL 会被忽略。 -
canonical 与 hreflang 打架。 这是隐蔽的杀手。每个 locale 页面的 canonical 必须自引用——
/de/produkt的 canonical 指向/de/produkt。如果每个 locale 都改为 canonical 指向英语版本,你就是在告诉 Google”这些是重复内容,只索引英语那个”,这直接与 hreflang 的”这些是不同的 locale 等价页”相矛盾。canonical 胜出,你的译文从索引里消失。规则:每个页面都用自引用 canonical + hreflang 组。 -
缺少
x-default。 并非严格必需,但没有它,Google 就没有为未匹配用户声明的兜底。永远包含一个x-default——通常指向一个语言选择页,或你最通用/默认语言的版本。 -
给重定向或非 200 的 URL 做注解。 组里的每个 URL 都必须返回
200 OK且可索引。把 hreflang 指向一个会 301 重定向或 404 的 URL,会破坏集群的那个节点。 -
交付方式混用不一致。 sitemap 说一套,HTML 头说另一套。选一个单一事实来源。
⚠️ 注意:hreflang 是聚类与路由提示,不是排名加成。它不会把一个弱页面抬上去。当它正确时,它会确保展示给每个搜索者的,是那个本就在排名的正确页面。预期是”用户现在落到正确的 locale”,而不是”流量翻倍”。
语言定向 vs 地区定向——别跟自己抢
最难的战略抉择是粒度要做多细。locale 页面越多并非越好;它会成倍增加维护量,并带来自我竞争的风险。
仅按语言定向(en、de、fr),当内容对该语言所有使用者真正一致、与国家无关时——同样的产品、同样的定价、同样的文案。一个德语页面同时服务德国、奥地利和瑞士,更简单,并把信号集中在单个 URL 上。
按语言 + 地区定向(en-US、en-GB),当同一语言在不同市场需要不同内容时:不同的价格或货币、不同的配送或可用性、地区特定的法律文本,或值得拆分的拼写/习惯差异(color/colour、fall/autumn)。
危险区是同一语言、多个地区、内容近乎相同——en-US 和 en-GB 页面只有一个价格字符串不同。在 Google 眼里它们看起来像争夺同一查询的重复内容,正是经典的自我蚕食陷阱。有两条出路:
- 差异化到足以撑起拆分——不同的定价、本地案例、市场特定的板块——并且接好正确的 hreflang,让 Google 按地区路由,而不是替你选。
- 或者收拢为仅语言(
en配x-default),如果市场之间真的没有区别。一个强页面胜过两个弱的双胞胎。
💡 提示:一个好用的测试——如果一个美国访客和一个英国访客被同一个页面服务得一样好,你就不需要分开的地区页面。 只在体验真正分化时才拆分。
工具与校验
hreflang 太容易出错,规模一大就无法靠肉眼核验。把它做进你的工作流。
- Google Search Console。 International Targeting / Page indexing 报告会直接从 Google 自己的解析中暴露 hreflang 错误(“no return tags”、“unknown language code”)。这是基准事实——Google 在告诉你它看到了什么。每次 locale 上线后都检查它。
- hreflang 校验器。 专用检查器(Merkle 的 hreflang tags testing tool、TechnicalSEO.com、Aleyda Solis 的生成器)接收一个 URL,校验互指性、代码有效性和绝对 URL。用生成器产出正确的初版组,再用校验器确认线上页面。
- 爬虫。 Screaming Frog、Sitebliss 或 Ahrefs 的站点审计会爬取整站,一次性在成千上万个 URL 上标出缺失的 return 标签、非 200 的备选,以及 canonical/hreflang 冲突——这是审计大型站点唯一可行的办法。
- 用
curl查头和快速看 head。 一次性核验 HTTP 头里的 hreflang 或某页的 head:
# Inspect HTTP Link-header hreflang (e.g. on a PDF)
curl -sI https://example.com/en/manual.pdf | grep -i '^link:'
# Pull every hreflang link from a rendered page
curl -s https://example.com/en/product | grep -o 'hreflang="[^"]*"'
🧑💻 开发者视角:加一个 CI 检查。爬取你构建出的产物,解析每个页面的 hreflang 组,在部署之前断言互指性、绝对 URL 和自引用 canonical。流水线里一段 30 行的脚本,能在 PR 阶段抓住缺失 return 标签的 bug,而不是三周后在 Search Console 里。
🧑💻 本站作为一个实例
本站本身就是双语的(英文和中文),所以它就靠这些规则运转。它用 Astro 的 i18n 支持构建:每篇指南都同时存在于 /en/ 和 /zh/ 路径前缀下(子目录——即上文的建议),由单一 locale 配置驱动,该配置把每个 locale 映射到它的 hreflang 值和路径前缀。那份配置就是开发者视角提示里说的单一事实来源——语言切换器、路由和 hreflang 标签都从它读取。
hreflang 注解由 Astro 的 sitemap 集成自动生成,而不是手写进每个页面头。当站点构建时,集成会遍历 locale 配置,为每个被翻译的页面生成完整的双向 xhtml:link 集群(包含 x-default),并写出一份 sitemap。新增一个 locale 是改一处配置,而不是横扫数百个文件——而且互指性由构造保证,因为生成器每次都生成对称的那组。
这正是框架第 7 层(高级与扩展)的实践——国际化 SEO 是一项高级关切,你在基本功扎实之后才叠加它。但它落地在更底层的第 2 层(构建层):i18n 路由和 sitemap 生成被接进站点的地基,而不是后来才硬装上去。如果你想了解静态产物和 sitemap 是怎么生成的机制,Edge SEO 和构建层指南讲了让这一切自动化的部署与生成流水线。
关键要点
- ✅ 默认用子目录(
/de/、/en-gb/)——它们集中权重、成本最低,也正是静态生成器天然产出的形态。把 ccTLD 留给强地理信任需求。 - ✅ 让每个 hreflang 组都双向且自引用,带绝对 URL和一个
x-default——一条断掉的 return 标签就让整个集群作废。 - ✅ 在每个 locale 上保持 canonical 自引用;绝不要把译文 canonical 到默认语言,否则它们会从索引里掉出去。
- ✅ 正确使用 ISO 代码——语言是 ISO 639-1,地区是 ISO 3166-1(
en-GB,不是en-UK)。 - ✅ 只在内容真正不同时才按地区拆分;否则按语言定向,避免自我蚕食。
- ✅ 持续校验——Search Console + 一个爬虫 + 一项 CI 互指性检查,因为 hreflang 是悄无声息地失败的。