< BACK 木质办公桌上褪色的欧洲纸质地图,旁边放着浓缩咖啡杯,阴天社论光线,35mm 胶片颗粒

Next.js i18n 2026:路由、hreflang 和 SEO

2023 年,我为一家柏林的客户制作了一个多语言电商网站。十二种语言,App Router,一切看起来整洁有序。三个月后,他们的德语产品页面在英国搜索结果中排名,他们的英语页面被提供给慕尼黑的用户。hreflang 标签在技术上是存在的。只是以一种让我花了一个尴尬的下午才理清的方式出错了。那次经历教会了我的关于 Next.js i18n 的知识,比任何文档页面都要多。

这不是一篇"这是官方文档的总结"的文章。这是我在 Seahawk Media 构建多语言网站时学到的东西,是人们一再遇到的问题,以及在 2026 年实际影响排名的具体决策。

---

App Router 改变了一切(并非全部都更好)

Pages Router 有内置的 i18n 支持。你在 next.config.js 中放入一个配置块,Next.js 会自动处理语言检测、子路径路由和 Link 组件的语言切换。这并不完美,但它是固执己见的,这意味着踩坑更少。

App Router 完全移除了原生 i18n 路由。next.config.js 中没有 i18n 键。没有自动语言区域检测。你现在需要自己负责完整的路由逻辑,而这正是大多数团队容易出问题的地方。

2026 年的最佳实践是在你的 app/ 目录根部使用 [locale] 动态段。比如 app/[locale]/page.tsx 。配合 next-intl 或通过 react-i18next 的 i18next,再加上一个读取 Accept-Language 请求头并相应重定向的中间件文件,你就有了一个可行的基础。

中间件为什么容易被误解

中间件文件在做两项人们经常混淆的工作。第一,语言区域检测:读取 Accept-Language 请求头(或 cookie、或 URL 前缀)并决定要提供哪种语言。第二,重定向或重写 URL,使用户最终访问的是 /de/produkte 而不是 /produkte 加上某种隐藏的语言状态。

这两项都必须在页面渲染之前进行。这听起来很明显。但我见过有些团队在 React 上下文提供程序内部执行语言区域检测,这意味着发送给 Googlebot 的初始 HTML 完全没有语言信号。搜索引擎爬虫看到一个页面,它会在根 URL 下索引它,突然之间你就陷入了规范标签的混乱。

把语言区域解析保留在中间件中。永远都要。

---

子路径 vs 子域名:选一个并坚持下去

永恒的争论。/en/about vs en.yoursite.com/about 。对于大多数项目,我推荐子路径路由,原因如下:它可以集中你的域名权重。指向你的 .com 的每个链接都会让所有语言版本受益。使用子域名的话,在谷歌眼中你实际上是在运营独立的网站,这意味着你需要为每个子域名独立建立权重。

有一个例外。如果你所在的市场非常看重本地 TLD(德国的 .de 、法国的 .fr ),国家代码顶级域名在本地可信度上会优于子路径和子域名两者。这不是一个 SEO 说法,这是一个转化率说法。我看过 A/B 测试,.de vs yourbrand.com/de 在排名完全相同的情况下仍然显著改变了信任指标。

但你无法在单个 Next.js 应用中轻松运行 ccTLD 设置。所以对于 99% 的项目来说:子路径、一个应用、一个域名。

---

hreflang:那个欺骗你的标签

这是我看到最多问题的地方。hreflang 在概念上并不难。它告诉搜索引擎:"这个页面在其他语言中有等效版本,它们在这里。"在实践中,实现有三种具体的失败模式,我一直在遇到。

失败模式 1:标签不相互关联

hreflang 集合中的每个页面都必须引用其他所有页面,包括自身。如果 /en/about 有一个指向 /de/about 的 hreflang,但 /de/about 没有一个指向回 /en/about 的 hreflang,Google 就会忽略整个集合。不是降低排名。是忽略。就像这些标签不存在一样。

Seahawk 去年的一个金融科技项目中,40% 的 hreflang 标签不相互关联,因为两个开发者在不同的组件中独立实现了它们而没有协调。修复花了一小时。排名恢复花了两个月。

失败模式 2:x-default 标签丢失或放置错误

x-default 用于不针对特定语言或地区的页面。通常是你的根目录 / 或语言选择器页面。有些团队将 x-default 放在他们的英文页面上,如果英文确实是你所有不匹配语言环境的备用语言,这没关系。但如果你为语言不支持的用户提供不同的页面,x-default 应该指向那里。

我见过 x-default 被完全省略的情况。Google 不会抛出错误。它只是自己决定为不匹配的查询显示哪个版本,这个决定通常是错误的。

失败模式 3:尾部斜杠和规范标签冲突

你的 hreflang 标签指向 https://yoursite.com/de/ueber-uns。同一页面上的规范标签指向 https://yoursite.com/de/ueber-uns/(注意尾部斜杠)。Google 将这两个视为不同的 URL。你的规范标签和 hreflang 现在指向不同的内容,创建了一个信号冲突,在 Search Console 中没有任何可见错误的情况下悄悄抑制排名。

在 next.config.js 中设置 trailingSlash,并保持一致。检查它。通过 curl 你自己的 URL 并读取输出来实际检查它。

Google Search Central 关于 hreflang 的文档确实值得完整阅读。这是那些难得一见的、具体到足以有用的官方文档之一。

---

在 Next.js 中规模化生成 hreflang

对于 5 语言网站,你可以手动编写 hreflang 标签。对于 3,000 个产品页面跨越 12 个地区,你需要一个系统化的方法。

这是我使用的方法:

  1. 在单个配置文件中定义你的地区列表一次。类似 locales.config.ts 导出一个数组:['en', 'de', 'fr', 'es', 'nl'...]。
  2. 构建一个实用函数 getHreflangAlternates(pathname: string),它接收一个与地区无关的路径(/about),并为每个地区返回一个 { hreflang, href } 对象数组。
  3. 在你的根 layout.tsx 中调用该函数,并通过 Next.js 的 metadata API 使用 alternates.languages 字段注入结果。这是在 Next.js 13.3 中添加的,它会自动在 <head> 中生成正确的 <link rel="alternate" hreflang="..."> 标签。
  4. 对于包含动态内容的页面(博客文章、产品页面)中并非每个语言区域都有翻译的情况,将 supportedLocales 数组传递给你的工具函数,仅为内容实际存在的语言区域生成标签。指向返回 404 的页面的 hreflang 标签比没有 hreflang 标签更糟。

metadata API 中的 alternates.languages 方法是我找到的处理这个问题最简洁的方式。不需要自定义 <Head> 操作,也不需要为标签本身引入第三方包。

---

语言区域检测而不显得烦人

这里有一个 UX 陷阱,同时也会影响 SEO。基于浏览器语言区域的激进自动重定向对两者都有害。

如果德国用户访问 yoursite.com/en/article,而你的中间件硬性将他们重定向到 /de/artikel 而没有征求同意,你就破坏了他们的预期导航。你也可能破坏了链接分享:有人分享了英文 URL,法国的收件人被转向法文,而他们本应看到的内容可能还没有法文版本。

我的原则是:检测、建议、不强制。使用 Accept-Language 标头在首次访问时设置合理的默认值(存储在 Cookie 中),但让用户手动覆盖它,并在随后的每次访问中尊重该覆盖。Cookie 优先于标头。

对于 SEO 来说:Googlebot 不发送有意义的 Accept-Language 标头。它会按原样爬取你的 URL。因此,请确保每个语言区域的 URL 都可以直接访问而不需要重定向,并且你的网站地图明确包含所有语言区域变体。

next-intl 中间件文档很好地涵盖了这种检测策略,这是我会向任何启动新项目的人推荐的地方。

---

多语言网站的网站地图

对于较小的网站,单个 sitemap.xml 文件包含所有语言版本就可以了。对于大型网站(跨多个语言版本有数万个网址),我会进行拆分:在 /sitemap.xml 处放置一个网站地图索引,然后在 /sitemaps/en.xml、/sitemaps/de.xml 等处放置按语言版本分开的网站地图文件。

每个 URL 条目应该包含 <xhtml:link rel="alternate"> 标签,与你的 hreflang 设置相对应。这虽然与页面内的标签重复,但它有助于 Googlebot 连接它尚未爬取过的页面。

在 Next.js 14+ 中,你可以通过 app/sitemap.ts 动态生成网站地图。返回类型支持 alternates.languages,其形状与元数据 API 相同。将其关联到你之前构建的 getHreflangAlternates 工具函数,你就能获得一致性。

一个具体数字:在拥有 8 个语言版本和 4,500 个产品的项目中,根据 GSC 爬取统计,拆分网站地图使每个语言版本的平均爬取时间减少了约 30%。不是突破性的改进,但很有意义。

---

翻译库:我实际在用的

有三个值得了解。next-intl 是我在 App Router 项目中的默认选择。它对服务器组件有一流的支持,正确处理复数形式,API 也很简洁。自 2023 年中期以来,我已在大约 40 个项目中使用过它。

react-i18next 经过充分测试,生态系统庞大,但它的设计思路是为客户端组件构建的,通过 i18next 核心添加服务器组件支持感觉是后来才加的,与 next-intl 的原生方式相比不够优雅。

如果你的翻译工作流涉及专业翻译人员而非 CMS,Lingui 值得一看。它的提取工具更好用。但对于大多数内容来自 Contentful 或 Sanity 的代理项目,next-intl 更简单。

我会避免的是:自己动手做。我在 2020 年做过一次,当时客户坚持要直接复用他们现有的 PHP 翻译文件。勉强可用,但我花了两年时间维护一些东西,现在 next-intl 用一个 useTranslations() 调用就搞定了。

---

FAQ

Next.js App Router 是否支持旧的 `next.config.js` i18n 配置块?

不支持。i18n 配置键特定于 Pages Router。如果你要迁移到 App Router,需要通过 [locale] 动态段和中间件文件来实现路由。App Router 项目中旧配置块会被默认忽略。

我应该在 URL 路径中使用 `lang` 还是依赖子域名?

对大多数项目来说,在 URL 路径中使用 lang(子路径路由如 /de/)是正确选择。子域名会分散你的域名权重,并需要单独的 DNS 配置。ccTLD 只有在本地信任信号确实在你的特定市场推动转化率时,才值得处理这种复杂性。

Googlebot 如何处理 JavaScript 渲染的语言版本内容?

有时处理得很糟。如果你的语言检测完全在 React 上下文中的客户端运行,而初始 HTML 中没有语言标记,Googlebot 可能会索引水合前的状态。始终在中间件中解析语言,并确保服务器渲染的 HTML 中 <html> 元素上有正确的 lang 属性。通过查看源代码来检查这一点,而不是使用浏览器开发者工具(它显示的是水合后的状态)。

对于没有语言选择器页面的网站,正确的 `x-default` hreflang 值应该是什么?

将其指向你的主要语言版本的主页,通常是 /en/。这向Google表明英文是你对不支持的语言用户的备用语言。这样做没有惩罚,只是一个Google可能会也可能不会遵守的软性偏好信号。

我可以在没有付费SEO工具的情况下验证我的hreflang实现吗?

可以。Google Search Console的URL检查工具会显示任何已索引网址的检测到的hreflang标签。它不会自动验证互惠性,但你可以手动检查几个代表性页面。对于批量验证,Screaming Frog(免费最多500个网址)在其hreflang标签页中处理互惠性检查。

---

让多语言Next.js正确运行是真正的费力工作。涉及配置文件、URL一致性,以及确保两个开发人员没有在不同组件中重复实现同样的东西。但那些我花时间正确实现的网站,其排名在各地区都能保持,而那些我走捷径的网站教会了我我在这里写下的经验教训。

顺便说一下,柏林的那个客户最终让他们的德文页面在德国获得排名。花了我们一个完整的重新爬取周期和大量的咖啡。

< BACK