创见博客
多语言网站中的 hreflang:前端实现与避坑指南
七崽爱吃小饼干2026/08/25阅读 0

打开一个多语言商品页,<head> 中至少应该看到类似下面的内容:

html
<link rel="canonical" href="https://example.com/zh-cn/product" />
<link rel="alternate" hreflang="zh-CN" href="https://example.com/zh-cn/product" />
<link rel="alternate" hreflang="en-US" href="https://example.com/en-us/product" />
<link rel="alternate" hreflang="x-default" href="https://example.com/product" />

这几行代码不会翻译页面,也不会替用户跳转。它们解决的是另一个问题:告诉搜索引擎,这几个 URL 是同一份内容面向不同语言或地区的版本。

对前端开发者来说,hreflang 的语法并不复杂。真正容易出错的是 URL 建模、页面间的双向关系,以及它和 canonical 的配合。

hreflang 到底解决什么问题

假设网站同时提供三个页面:

text
https://example.com/zh-cn/product
https://example.com/en-us/product
https://example.com/en-gb/product

它们介绍的是同一款产品,但语言、货币、配送范围或文案有所不同。搜索引擎需要知道:

  • 中文用户更适合看到 zh-CN 页面;
  • 美国英语用户更适合看到 en-US 页面;
  • 英国英语用户更适合看到 en-GB 页面;
  • 无法匹配时应该使用哪个默认入口。

hreflang 就是这组对应关系的声明。它主要帮助搜索引擎选择更合适的搜索结果,而不是提高页面排名的直接开关。

一组正确的声明长什么样

中文页面可以这样写:

html
<head>
  <link rel="canonical" href="https://example.com/zh-cn/product" />

  <link rel="alternate" hreflang="zh-CN" href="https://example.com/zh-cn/product" />
  <link rel="alternate" hreflang="en-US" href="https://example.com/en-us/product" />
  <link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/product" />
  <link rel="alternate" hreflang="x-default" href="https://example.com/product" />
</head>

英文页面也要输出相同的完整映射,而不是只放一条指向中文页面的链接。

这里有三个关键规则。

1. 页面必须声明自己

zh-CN 页面需要包含指向自身的 zh-CN 条目。不要因为当前语言已经明确,就省略这一行。

2. 不同版本必须互相引用

如果页面 A 声明页面 B 是自己的英文版本,那么页面 B 也应该声明页面 A 是自己的中文版本。这通常被称为返回链接或双向引用。

只在默认语言页面维护一份 hreflang,其他语言页面什么都不写,会让整组关系变得不完整。

3. href 应该是可索引的绝对 URL

推荐使用完整地址:

html
<link rel="alternate" hreflang="ja" href="https://example.com/ja/product" />

目标 URL 应该返回正常页面,而不是 404、登录页或多次重定向。它也不应该被 robots.txt 或 noindex 阻止。

语言代码和地区代码怎么写

常见格式是:

text
语言代码-地区代码

例如:

text
zh-CN
zh-TW
en
en-US
en-GB
ja
ko

语言部分使用 ISO 639-1 代码,地区部分使用 ISO 3166-1 Alpha-2 代码。zh 表示中文,CN 表示中国大陆。

一个常见错误是写成 cn 并把它当成中文。地区不能替代语言。如果网站只有一个英文版本,直接使用 en 即可;只有当内容确实因地区而不同时,才需要继续区分 en-US 和 en-GB。

x-default 应该指向哪里

x-default 表示没有任何语言或地区匹配时使用的默认 URL:

html
<link rel="alternate" hreflang="x-default" href="https://example.com/product" />

它通常指向以下页面之一:

  • 语言选择页;
  • 不带地区前缀的默认入口;
  • 根据用户选择进行分流的页面。

不要把 x-default 理解为英语。它表达的是兜底关系,而不是某一种语言。

hreflang 和 canonical 不是二选一

多语言 SEO 中最常见的配置错误,是让所有语言页面的 canonical 都指向默认语言:

text
/zh-cn/product -> canonical 到 /en-us/product
/en-us/product -> canonical 到 /en-us/product

这相当于告诉搜索引擎,中文页面不是独立的规范页面;与此同时,hreflang 又声称它是应该提供给中文用户的版本。两组信号互相冲突。

通常,每个语言页面应该 canonical 到自己:

text
/zh-cn/product -> canonical 到 /zh-cn/product
/en-us/product -> canonical 到 /en-us/product

然后由 hreflang 描述这些规范页面之间的语言关系。

React 中如何生成

如果站点使用 React,可以先把语言与 URL 的对应关系建模,再渲染 <link>:

tsx
const alternateUrls = [
  { locale: 'zh-CN', url: 'https://example.com/zh-cn/product' },
  { locale: 'en-US', url: 'https://example.com/en-us/product' },
  { locale: 'en-GB', url: 'https://example.com/en-gb/product' },
  { locale: 'x-default', url: 'https://example.com/product' },
]

export function HreflangLinks() {
  return alternateUrls.map(({ locale, url }) => (
    <link key={locale} rel="alternate" hrefLang={locale} href={url} />
  ))
}

注意 JSX 中的属性名是 hrefLang,最终生成的 HTML 属性仍然是 hreflang。

更重要的是,这些标签应该在 SSR 或 SSG 阶段进入初始 HTML。不要等页面挂载后再通过 useEffect 插入。SEO 元数据不应该依赖客户端 JavaScript 才能出现。

Next.js App Router 的实现

静态页面可以直接使用 Metadata API:

tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  alternates: {
    canonical: 'https://example.com/zh-cn/product',
    languages: {
      'zh-CN': 'https://example.com/zh-cn/product',
      'en-US': 'https://example.com/en-us/product',
      'en-GB': 'https://example.com/en-gb/product',
      'x-default': 'https://example.com/product',
    },
  },
}

文章页、商品页等动态路由,可以使用 generateMetadata:

tsx
import type { Metadata } from 'next'

const siteUrl = 'https://example.com'

export async function generateMetadata({
  params,
}: {
  params: Promise<{ locale: string; slug: string }>
}): Promise<Metadata> {
  const { locale, slug } = await params

  return {
    alternates: {
      canonical: `${siteUrl}/${locale}/${slug}`,
      languages: {
        'zh-CN': `${siteUrl}/zh-cn/${slug}`,
        'en-US': `${siteUrl}/en-us/${slug}`,
        'en-GB': `${siteUrl}/en-gb/${slug}`,
        'x-default': `${siteUrl}/${slug}`,
      },
    },
  }
}

实际项目中,不要默认每篇内容都拥有所有语言版本。如果某篇文章尚未翻译,就不应生成一个不存在的目标 URL。语言列表最好来自 CMS 中真实存在的翻译关系,而不是写死全部站点语言。

例如接口可以返回:

ts
type Translation = {
  locale: string
  slug: string
}

页面只根据 translations 生成可用条目。这样可以避免发布流程不同步时产生大量 404 链接。

URL 结构比标签更早决定成败

前端添加 <link> 之前,团队需要先确定每个语言版本是否拥有稳定、唯一、可直接访问的 URL。常见方案包括:

text
example.com/zh-cn/product
zh-cn.example.com/product
example.cn/product

三种方案都能配合 hreflang。真正需要避免的是只有一个 URL,然后根据 Cookie 或浏览器语言返回不同内容。

如果 https://example.com/product 有时返回中文、有时返回英文,搜索引擎无法稳定抓取各语言版本,用户也无法分享一个明确的语言地址。前端路由和服务端渲染应该保证:同一个 URL 对应稳定的主要语言内容。

自动语言跳转也要谨慎。强制按照 IP 或 Accept-Language 重定向,可能阻止爬虫访问其他版本。更稳妥的方式是提供语言建议,同时保留用户访问和切换任意版本的能力。

上线前检查清单

可以逐页检查以下项目:

  • 每种已发布语言都有独立且稳定的 URL;
  • 每个页面都包含指向自身的 hreflang;
  • 同组页面输出相同的完整语言映射;
  • 页面之间存在双向引用;
  • href 使用绝对 URL;
  • 目标页面返回 200,且允许索引;
  • 语言和地区代码有效;
  • 每个语言页面的 canonical 通常指向自身;
  • x-default 指向明确的默认入口;
  • 初始 HTML 中已经存在这些标签;
  • 未发布的翻译不会被加入映射。

还可以直接查看服务端返回内容,而不是只看浏览器 Elements 面板:

bash
curl -s https://example.com/zh-cn/product | grep -i hreflang

Elements 面板展示的是 JavaScript 执行后的 DOM。curl 或“查看网页源代码”更接近搜索引擎最先收到的 HTML,也更容易发现元数据只在客户端生成的问题。

最后记住三件事

第一,hreflang 声明的是页面之间的语言和地区关系,不负责翻译,也不负责跳转。

第二,同一组页面必须自引用、互相引用,并且只包含真实可访问的翻译版本。

第三,canonical 和 hreflang 各自解决不同问题。让每个语言页面 canonical 到自己,再用 hreflang 把它们连接起来,通常是最清晰、最稳定的配置。

评论
0/100