国际化网站 hreflang 配置完全指南
当你的网站面向多个语言或地区的用户时,如何让搜索引擎准确理解每个页面的目标受众,并在搜索结果中展示正确的语言版本?这就是 hreflang 标签要解决的核心问题。
配置正确的 hreflang 不仅能提升用户体验——让法国用户看到法语版本,让日本用户看到日语版本——还能避免重复内容问题、提升各地区的搜索排名,是国际化 SEO 的基石。
本文将系统讲解 hreflang 的工作原理、配置方法、常见陷阱以及在 Nuxt 框架中的最佳实践。
hreflang 的核心概念
什么是 hreflang?
hreflang 是一种 HTML 链接属性,用于告诉搜索引擎:「这个页面还有其他语言/地区的版本,它们分别在这些 URL」。
<link rel="alternate" hreflang="en" href="https://example.com/page" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/page" />
<link rel="alternate" hreflang="ja" href="https://example.com/ja/page" />
这段代码告诉搜索引擎:
- 英文版本在
https://example.com/page - 中文版本在
https://example.com/zh/page - 日语版本在
https://example.com/ja/page
hreflang vs 用户语言偏好
hreflang 不会自动重定向用户。它的作用是:
- 搜索结果定向:Google 会根据用户的搜索语言/位置,在搜索结果中优先展示对应的语言版本
- 消除重复内容:告诉搜索引擎不同语言版本是同一内容的变体,而非重复页面
- 传递页面权重:不同语言版本可以互相传递 SEO 权重
用户访问网站后的语言切换,应该通过其他机制实现(语言选择器、浏览器语言检测等)。
语言标签格式
hreflang 使用 ISO 标准 定义语言和地区:
| 格式 | 示例 | 含义 |
|---|---|---|
语言代码 | en | 英语(不限地区) |
语言-地区 | en-US | 美式英语 |
语言-地区 | zh-CN | 简体中文(中国大陆) |
语言-地区 | zh-TW | 繁体中文(台湾地区) |
关键规则:
语言代码:ISO 639-1(2字母,小写)
地区代码:ISO 3166-1 Alpha-2(2字母,大写)
常见语言代码:
const languageCodes = {
en: 'English',
zh: '中文',
ja: '日本語',
ko: '한국어',
fr: 'Français',
de: 'Deutsch',
es: 'Español',
pt: 'Português',
ru: 'Русский',
ar: 'العربية'
}
hreflang 的实现方式
有三种方式可以声明 hreflang,选择哪种取决于网站规模和技术架构。
方式一:HTML <link> 标签
最直接的方式,适合小型网站或需要精细控制的场景。
<head>
<!-- 当前页面的所有语言版本 -->
<link rel="alternate" hreflang="en" href="https://example.com/products" />
<link rel="alternate" hreflang="en-GB" href="https://example.com/uk/products" />
<link rel="alternate" hreflang="zh-CN" href="https://example.com/cn/products" />
<link rel="alternate" hreflang="zh-TW" href="https://example.com/tw/products" />
<link rel="alternate" hreflang="ja" href="https://example.com/jp/products" />
<!-- x-default:默认版本(当没有匹配的语言时) -->
<link rel="alternate" hreflang="x-default" href="https://example.com/products" />
</head>
x-default 的作用:
x-default 指定当用户的语言/地区没有对应版本时,应该展示哪个页面。通常设置为:
- 英文版本(国际通用)
- 语言选择页面
- 主要市场版本
方式二:HTTP 头信息
适合非 HTML 资源(如 PDF 文件)或无法修改 HTML 的场景。
HTTP/1.1 200 OK
Link: <https://example.com/file.pdf>; rel="alternate"; hreflang="en",
<https://example.com/zh/file.pdf>; rel="alternate"; hreflang="zh",
<https://example.com/ja/file.pdf>; rel="alternate"; hreflang="ja"
在 Nginx 中配置:
location ~ ^/documents/(.+\.pdf)$ {
add_header Link '<https://example.com/documents/$1>; rel="alternate"; hreflang="en", <https://example.com/zh/documents/$1>; rel="alternate"; hreflang="zh"';
}
方式三:XML Sitemap
最推荐的方式,尤其适合大规模多语言站点。
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://example.com/products</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/products"/>
<xhtml:link rel="alternate" hreflang="zh" href="https://example.com/zh/products"/>
<xhtml:link rel="alternate" hreflang="ja" href="https://example.com/jp/products"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/products"/>
</url>
<url>
<loc>https://example.com/zh/products</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/products"/>
<xhtml:link rel="alternate" hreflang="zh" href="https://example.com/zh/products"/>
<xhtml:link rel="alternate" hreflang="ja" href="https://example.com/jp/products"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/products"/>
</url>
</urlset>
Sitemap 方式的优势:
- 集中管理:所有 hreflang 映射在一处维护
- 易于自动化:可通过脚本批量生成
- 减少 HTML 体积:不需要在每个页面嵌入大量 link 标签
- 更新方便:添加新语言时只需更新 Sitemap
URL 结构策略
在配置 hreflang 之前,需要确定多语言 URL 的组织方式。
策略对比
| 策略 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| 子目录 | example.com/zh/ | 共享域名权重、易于管理 | 地区定向不够明确 |
| 子域名 | zh.example.com | 独立性好、可分别托管 | 权重分散、维护成本高 |
| ccTLD | example.cn | 地区信号最强 | 成本高、权重独立、管理复杂 |
| 参数 | example.com?lang=zh | 实现最简单 | Google 不推荐、SEO 不友好 |
推荐方案:子目录结构
对于大多数站点,子目录 是最佳选择:
https://example.com/ → 英文(默认)
https://example.com/zh/ → 简体中文
https://example.com/zh-tw/ → 繁体中文
https://example.com/ja/ → 日语
https://example.com/ko/ → 韩语
URL 映射规则:
// utils/i18n-url.js
const localeConfig = {
default: 'en',
locales: ['en', 'zh', 'zh-tw', 'ja', 'ko'],
urlMapping: {
en: '', // 默认语言不加前缀
zh: '/zh',
'zh-tw': '/zh-tw',
ja: '/ja',
ko: '/ko'
}
}
/**
* 生成指定语言的 URL
*/
export function getLocalizedUrl(path, locale) {
const prefix = localeConfig.urlMapping[locale] || ''
const normalizedPath = path.startsWith('/') ? path : `/${path}`
return `${prefix}${normalizedPath}`
}
/**
* 从 URL 中解析语言
*/
export function getLocaleFromUrl(url) {
for (const [locale, prefix] of Object.entries(localeConfig.urlMapping)) {
if (prefix && url.startsWith(prefix + '/')) {
return locale
}
}
return localeConfig.default
}
/**
* 生成所有语言版本的 URL 映射
*/
export function getAllLanguageVersions(path) {
return localeConfig.locales.map(locale => ({
locale,
url: `https://example.com${getLocalizedUrl(path, locale)}`
}))
}
Nuxt 中的 hreflang 实现
使用 @nuxtjs/i18n 模块
Nuxt 的 i18n 模块提供了完整的 hreflang 支持。
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
locales: [
{
code: 'en',
iso: 'en',
name: 'English',
file: 'en.json'
},
{
code: 'zh',
iso: 'zh-CN',
name: '简体中文',
file: 'zh.json'
},
{
code: 'zh-tw',
iso: 'zh-TW',
name: '繁體中文',
file: 'zh-tw.json'
},
{
code: 'ja',
iso: 'ja',
name: '日本語',
file: 'ja.json'
}
],
defaultLocale: 'en',
strategy: 'prefix_except_default',
// 可选策略:
// 'prefix' - 所有语言都加前缀
// 'prefix_except_default' - 默认语言无前缀
// 'prefix_and_default' - 默认语言可选前缀
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'i18n_redirected',
redirectOn: 'root', // 只在首页重定向
alwaysRedirect: false
},
// 自动生成 hreflang 标签
seo: true,
baseUrl: 'https://example.com'
}
})
自定义 hreflang Composable
如果需要更精细的控制,可以创建自定义 composable:
// composables/useHreflang.ts
interface HreflangLink {
rel: 'alternate'
hreflang: string
href: string
}
interface LocaleConfig {
code: string
iso: string
domain?: string
}
export function useHreflang() {
const route = useRoute()
const config = useRuntimeConfig()
const locales: LocaleConfig[] = [
{ code: 'en', iso: 'en' },
{ code: 'zh', iso: 'zh-CN' },
{ code: 'zh-tw', iso: 'zh-TW' },
{ code: 'ja', iso: 'ja' },
{ code: 'ko', iso: 'ko' }
]
const baseUrl = config.public.siteUrl || 'https://example.com'
/**
* 获取指定语言的页面路径
*/
function getLocalePath(locale: string): string {
const currentPath = route.path
// 移除当前语言前缀
let cleanPath = currentPath
for (const loc of locales) {
if (loc.code !== 'en' && currentPath.startsWith(`/${loc.code}/`)) {
cleanPath = currentPath.replace(`/${loc.code}`, '')
break
}
if (loc.code !== 'en' && currentPath === `/${loc.code}`) {
cleanPath = '/'
break
}
}
// 添加目标语言前缀
if (locale === 'en') {
return cleanPath || '/'
}
return `/${locale}${cleanPath}`
}
/**
* 生成所有 hreflang 链接
*/
function generateHreflangLinks(): HreflangLink[] {
const links: HreflangLink[] = []
// 各语言版本
for (const locale of locales) {
links.push({
rel: 'alternate',
hreflang: locale.iso,
href: `${baseUrl}${getLocalePath(locale.code)}`
})
}
// x-default(默认英文版本)
links.push({
rel: 'alternate',
hreflang: 'x-default',
href: `${baseUrl}${getLocalePath('en')}`
})
return links
}
/**
* 设置页面 hreflang 元标签
*/
function setHreflangMeta() {
const links = generateHreflangLinks()
useHead({
link: links.map(link => ({
rel: link.rel,
hreflang: link.hreflang,
href: link.href
}))
})
}
return {
locales,
getLocalePath,
generateHreflangLinks,
setHreflangMeta
}
}
在页面中使用:
<!-- pages/products.vue -->
<script setup lang="ts">
const { setHreflangMeta } = useHreflang()
// 自动设置 hreflang 标签
setHreflangMeta()
</script>
<template>
<div>
<h1>{{ $t('products.title') }}</h1>
<!-- 页面内容 -->
</div>
</template>
动态生成多语言 Sitemap
// server/routes/sitemap.xml.ts
import { SitemapStream, streamToPromise } from 'sitemap'
export default defineEventHandler(async (event) => {
const baseUrl = 'https://example.com'
const locales = ['en', 'zh', 'zh-tw', 'ja', 'ko']
// 获取所有页面
const pages = [
'/',
'/products',
'/about',
'/contact',
'/blog'
]
const sitemap = new SitemapStream({ hostname: baseUrl })
for (const page of pages) {
// 为每个语言版本创建条目
for (const locale of locales) {
const localePath = locale === 'en' ? page : `/${locale}${page}`
sitemap.write({
url: localePath,
changefreq: 'weekly',
priority: page === '/' ? 1.0 : 0.8,
links: locales.map(loc => ({
lang: getIsoCode(loc),
url: loc === 'en' ? `${baseUrl}${page}` : `${baseUrl}/${loc}${page}`
}))
})
}
}
sitemap.end()
const xml = await streamToPromise(sitemap)
setResponseHeader(event, 'content-type', 'application/xml')
return xml.toString()
})
function getIsoCode(locale: string): string {
const mapping: Record<string, string> = {
en: 'en',
zh: 'zh-CN',
'zh-tw': 'zh-TW',
ja: 'ja',
ko: 'ko'
}
return mapping[locale] || locale
}
地区与语言的高级场景
场景一:相同语言不同地区
针对使用相同语言但需要不同内容的地区:
<!-- 英语系国家 -->
<link rel="alternate" hreflang="en-US" href="https://example.com/us/page" />
<link rel="alternate" hreflang="en-GB" href="https://example.com/uk/page" />
<link rel="alternate" hreflang="en-AU" href="https://example.com/au/page" />
<!-- 西班牙语系国家 -->
<link rel="alternate" hreflang="es-ES" href="https://example.com/es/page" />
<link rel="alternate" hreflang="es-MX" href="https://example.com/mx/page" />
<link rel="alternate" hreflang="es-AR" href="https://example.com/ar/page" />
为什么需要区分?
- 货币和价格不同(美元 vs 英镑)
- 法律条款差异
- 文化习惯不同(日期格式、度量单位)
- 产品可用性不同
场景二:仅语言定向
当内容对所有地区相同,仅需区分语言:
<!-- 只指定语言,不限地区 -->
<link rel="alternate" hreflang="en" href="https://example.com/page" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/page" />
<link rel="alternate" hreflang="ja" href="https://example.com/ja/page" />
场景三:混合策略
对重点市场精确定向,其他市场宽泛定向:
<!-- 重点市场:精确定向 -->
<link rel="alternate" hreflang="en-US" href="https://example.com/page" />
<link rel="alternate" hreflang="zh-CN" href="https://example.com/cn/page" />
<!-- 其他市场:宽泛定向 -->
<link rel="alternate" hreflang="ja" href="https://example.com/ja/page" />
<link rel="alternate" hreflang="ko" href="https://example.com/ko/page" />
<!-- 默认版本 -->
<link rel="alternate" hreflang="x-default" href="https://example.com/page" />
常见错误与排查
错误一:双向确认缺失
hreflang 必须是双向确认的。如果 A 页面声明有 B 版本,B 页面也必须声明有 A 版本。
错误示例:
<!-- 英文页面 -->
<link rel="alternate" hreflang="zh" href="https://example.com/zh/page" />
<!-- 中文页面(缺少确认!) -->
<!-- 没有声明英文版本 -->
正确示例:
<!-- 英文页面 -->
<link rel="alternate" hreflang="en" href="https://example.com/page" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/page" />
<!-- 中文页面(双向确认) -->
<link rel="alternate" hreflang="en" href="https://example.com/page" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/page" />
错误二:自引用缺失
每个页面的 hreflang 列表必须包含指向自身的链接。
<!-- 当前页面是中文版 -->
<link rel="alternate" hreflang="en" href="https://example.com/page" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/page" /> <!-- 自引用 -->
<link rel="alternate" hreflang="ja" href="https://example.com/ja/page" />
错误三:语言代码格式错误
<!-- 错误:使用了无效的代码 -->
<link rel="alternate" hreflang="cn" href="..." /> <!-- cn 是国家代码,不是语言 -->
<link rel="alternate" hreflang="zh_CN" href="..." /> <!-- 应该用连字符,不是下划线 -->
<link rel="alternate" hreflang="ZH-CN" href="..." /> <!-- 语言代码应该小写 -->
<!-- 正确 -->
<link rel="alternate" hreflang="zh-CN" href="..." />
错误四:指向非规范 URL
hreflang 应指向规范 URL(canonical URL),避免指向重定向页面或参数变体。
<!-- 错误:指向会重定向的 URL -->
<link rel="alternate" hreflang="zh" href="https://example.com/zh/page/" /> <!-- 可能301到无斜杠版本 -->
<!-- 正确:指向最终的规范 URL -->
<link rel="alternate" hreflang="zh" href="https://example.com/zh/page" />
错误排查工具
1. Google Search Console
路径:URL 检查 → 输入页面 URL → 查看 hreflang 标签 部分
2. Ahrefs Site Audit
检测项:
- 缺失的反向 hreflang
- 无效的语言代码
- 指向 4xx/5xx 的 hreflang
3. 自定义验证脚本
// scripts/validate-hreflang.ts
import { JSDOM } from 'jsdom'
interface ValidationResult {
url: string
errors: string[]
warnings: string[]
}
async function validateHreflang(urls: string[]): Promise<ValidationResult[]> {
const results: ValidationResult[] = []
const hreflangMap = new Map<string, Map<string, string>>()
// 第一遍:收集所有 hreflang 声明
for (const url of urls) {
const response = await fetch(url)
const html = await response.text()
const dom = new JSDOM(html)
const links = dom.window.document.querySelectorAll('link[rel="alternate"][hreflang]')
const pageHreflangs = new Map<string, string>()
links.forEach(link => {
const hreflang = link.getAttribute('hreflang')
const href = link.getAttribute('href')
if (hreflang && href) {
pageHreflangs.set(hreflang, href)
}
})
hreflangMap.set(url, pageHreflangs)
}
// 第二遍:验证双向确认
for (const [url, hreflangs] of hreflangMap) {
const errors: string[] = []
const warnings: string[] = []
// 检查自引用
let hasSelfReference = false
for (const [, href] of hreflangs) {
if (href === url || href === url + '/') {
hasSelfReference = true
break
}
}
if (!hasSelfReference) {
errors.push('缺少自引用 hreflang')
}
// 检查双向确认
for (const [lang, targetUrl] of hreflangs) {
if (targetUrl === url) continue
const targetHreflangs = hreflangMap.get(targetUrl)
if (!targetHreflangs) {
warnings.push(`目标页面 ${targetUrl} 未被扫描`)
continue
}
let hasConfirmation = false
for (const [, confirmUrl] of targetHreflangs) {
if (confirmUrl === url || confirmUrl === url + '/') {
hasConfirmation = true
break
}
}
if (!hasConfirmation) {
errors.push(`${lang} 版本 (${targetUrl}) 缺少反向确认`)
}
}
// 检查 x-default
if (!hreflangs.has('x-default')) {
warnings.push('建议添加 x-default')
}
results.push({ url, errors, warnings })
}
return results
}
// 使用示例
const urls = [
'https://example.com/page',
'https://example.com/zh/page',
'https://example.com/ja/page'
]
validateHreflang(urls).then(results => {
for (const result of results) {
console.log(`\n${result.url}`)
result.errors.forEach(e => console.log(` ❌ ${e}`))
result.warnings.forEach(w => console.log(` ⚠️ ${w}`))
}
})
大规模站点的自动化管理
数据库驱动的 hreflang
对于内容来自 CMS 的站点,hreflang 应从数据源自动生成:
// server/api/page-hreflang/[slug].ts
import { prisma } from '~/lib/prisma'
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug')
// 查询页面的所有语言版本
const page = await prisma.page.findUnique({
where: { slug },
include: {
translations: {
include: {
locale: true
}
}
}
})
if (!page) {
throw createError({ statusCode: 404 })
}
const baseUrl = 'https://example.com'
// 生成 hreflang 映射
const hreflangLinks = page.translations.map(t => ({
hreflang: t.locale.isoCode,
href: t.locale.isDefault
? `${baseUrl}/${page.slug}`
: `${baseUrl}/${t.locale.code}/${page.slug}`
}))
// 添加 x-default
hreflangLinks.push({
hreflang: 'x-default',
href: `${baseUrl}/${page.slug}`
})
return hreflangLinks
})
批量 Sitemap 生成
// scripts/generate-hreflang-sitemap.ts
import { createWriteStream } from 'fs'
import { SitemapStream } from 'sitemap'
interface PageTranslation {
slug: string
translations: {
locale: string
isoCode: string
url: string
}[]
}
async function generateSitemap() {
const baseUrl = 'https://example.com'
// 从 API 或数据库获取所有页面
const pages: PageTranslation[] = await fetchAllPages()
const sitemap = new SitemapStream({ hostname: baseUrl })
const writeStream = createWriteStream('./public/sitemap.xml')
sitemap.pipe(writeStream)
for (const page of pages) {
for (const translation of page.translations) {
sitemap.write({
url: translation.url,
changefreq: 'weekly',
priority: 0.8,
links: page.translations.map(t => ({
lang: t.isoCode,
url: `${baseUrl}${t.url}`
}))
})
}
}
sitemap.end()
return new Promise((resolve, reject) => {
writeStream.on('finish', resolve)
writeStream.on('error', reject)
})
}
// 定期运行生成任务
generateSitemap()
.then(() => console.log('Sitemap 生成完成'))
.catch(console.error)
CI/CD 集成验证
# .github/workflows/hreflang-validation.yml
name: Validate Hreflang
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build site
run: npm run build
- name: Start preview server
run: npm run preview &
- name: Wait for server
run: sleep 10
- name: Validate hreflang tags
run: |
node scripts/validate-hreflang.js \
--urls-file urls.txt \
--report-file hreflang-report.json
- name: Check for errors
run: |
if grep -q '"errors":\[.*\]' hreflang-report.json; then
echo "Hreflang validation failed"
cat hreflang-report.json
exit 1
fi
- name: Upload report
uses: actions/upload-artifact@v4
with:
name: hreflang-report
path: hreflang-report.json
最佳实践清单
实施前准备
- 确定 URL 结构策略(子目录/子域名/ccTLD)
- 定义语言-地区矩阵
- 确认每个页面的翻译对应关系
- 选择实现方式(HTML/Sitemap/HTTP头)
配置检查项
- 所有 hreflang 链接双向确认
- 每个页面包含自引用
- 包含 x-default 版本
- 语言代码格式正确(ISO 639-1 + ISO 3166-1)
- URL 指向规范版本(无重定向)
- 目标 URL 可访问(200 状态码)
监控维护
- 定期在 Search Console 检查 hreflang 问题
- 新增/删除语言版本时同步更新
- 页面 URL 变更时更新 hreflang
- 自动化验证集成到 CI/CD
性能考量
- 大型站点优先使用 Sitemap 方式
- HTML 标签方式注意页面体积
- 缓存 hreflang 计算结果
总结
hreflang 是国际化 SEO 的核心技术,正确配置能够:
- 提升搜索可见性:让各地区用户在搜索结果中看到正确的语言版本
- 避免重复内容惩罚:告诉搜索引擎不同语言版本是等价的
- 改善用户体验:减少用户手动切换语言的需求
关键要点回顾:
- 使用 ISO 标准的语言和地区代码
- 确保所有 hreflang 声明双向确认
- 每个页面包含自引用和 x-default
- 大型站点优先使用 Sitemap 方式
- 建立自动化验证机制
配合 Nuxt 的 i18n 模块,可以自动处理大部分 hreflang 逻辑,同时保留手动干预的灵活性。记住:hreflang 配置正确与否,直接影响多语言站点的国际搜索表现。


