切换语言
切换主题

Next.js 多语言 SEO 优化完全指南:让搜索引擎正确收录每种语言

引言

你是否遇到过这样的困扰:

  • 精心制作了多语言网站,但搜索引擎总是显示错误的语言版本
  • 用户搜索中文内容,点击后却跳转到英文页面
  • 不同语言版本在搜索结果中互相竞争,导致整体排名下降

这些都是多语言网站 SEO 配置不当导致的典型问题。根据 Google 的统计数据,超过 60% 的多语言网站存在 hreflang 配置错误,严重影响了网站的国际化效果和用户体验。

60%+
多语言网站配置错误率
超过60%的多语言网站存在hreflang配置错误

本文将深入讲解如何在 Next.js 中正确实现多语言 SEO 优化,包括:

  • hreflang 标签的正确配置方法 - 避免语言版本混淆
  • 多语言 Sitemap 的生成策略 - 加速搜索引擎索引
  • URL 结构的最佳实践 - 选择最优的国际化方案
  • 常见错误的排查和修复 - 快速定位并解决问题

无论你使用的是 Pages Router 还是 App Router,都能找到对应的解决方案。

一、理解多语言 SEO 的核心概念

1.1 什么是 hreflang

hreflang 是一个 HTML 属性,用于告诉搜索引擎页面的目标语言和地区。它的主要作用包括:

  1. 防止内容重复惩罚 - 告诉搜索引擎不同语言版本是同一内容的翻译,而非重复内容
  2. 精准匹配用户 - 根据用户的语言和地区设置,显示最合适的页面版本
  3. 提升用户体验 - 避免用户看到错误语言版本的内容

1.2 Google 如何处理多语言内容

当 Google 爬虫访问你的多语言网站时,会执行以下步骤:

  1. 检测页面的语言(通过 HTML lang 属性、hreflang 标签、页面内容分析)
  2. 查找 hreflang 标签,理解不同语言页面之间的关系
  3. 根据用户的语言偏好,在搜索结果中显示对应的版本
  4. 合并不同语言版本的 SEO 权重(而非让它们互相竞争)

1.3 常见的 SEO 错误案例

错误 1:缺少 hreflang 标签

<!-- ❌ 错误:没有 hreflang 标签 -->
<head>
  <title>My Website</title>
  <link rel="canonical" href="https://example.com/en/about" />
</head>

后果:搜索引擎无法识别页面的语言关系,可能导致错误的语言版本出现在搜索结果中。

错误 2:hreflang 配置不对称

<!-- 英文页面 -->
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />

<!-- ❌ 中文页面 - 错误:缺少 hreflang 标签 -->
<!-- 必须在每个语言版本都配置完整的 hreflang -->

后果:Google 要求 hreflang 必须是对称的,单向配置会被忽略。

错误 3:使用错误的语言代码

<!-- ❌ 错误:使用了不规范的语言代码 -->
<link rel="alternate" hreflang="cn" href="..." /> <!-- 应该是 zh -->
<link rel="alternate" hreflang="en-us" href="..." /> <!-- 应该是 en-US,注意大小写 -->

后果:搜索引擎无法识别语言代码,hreflang 配置失效。

二、URL 策略选择

在实现多语言网站之前,首先要选择合适的 URL 策略。这个决定会影响到 SEO、用户体验和技术实现的方方面面。

2.1 三种主流 URL 策略对比

策略示例SEO 影响实现难度推荐指数
子目录example.com/en/
example.com/zh/
⭐⭐⭐⭐⭐ 最佳⭐⭐⭐ 中等⭐⭐⭐⭐⭐
子域名en.example.com
zh.example.com
⭐⭐⭐ 一般⭐⭐⭐⭐ 复杂⭐⭐⭐
URL 参数example.com?lang=en⭐⭐ 较差⭐⭐⭐⭐⭐ 简单⭐⭐

2.2 各策略的详细分析

方案 1:子目录策略(推荐)

优点:

  • SEO 权重集中在主域名,有利于整体排名提升
  • 配置简单,无需额外的域名管理和 SSL 证书
  • 易于维护和扩展,代码统一部署
  • Next.js 原生支持,实现简单

缺点:

  • 所有语言共享同一域名,无法针对特定市场做 DNS 优化

Next.js 实现:

// next.config.js
module.exports = {
  i18n: {
    locales: ['en', 'zh', 'ja', 'de'],
    defaultLocale: 'en',
    localeDetection: true // 自动检测用户语言
  }
}

方案 2:子域名策略

优点:

  • 可以针对不同市场部署到不同的服务器(例如中国区单独部署)
  • 技术栈可以独立,灵活性更高
  • 便于 CDN 和地理位置优化

缺点:

  • SEO 权重分散,需要独立建设每个子域名的权重
  • 需要额外的域名管理和 SSL 证书
  • 实现和维护成本较高

方案 3:URL 参数策略(不推荐)

优点:

  • 实现最简单

缺点:

  • SEO 效果最差,搜索引擎可能忽略参数
  • 用户体验不好,URL 不够友好
  • 难以进行 CDN 缓存优化
  • 无法在搜索结果中区分语言版本

结论:

对于大多数项目,强烈推荐使用子目录策略。它在 SEO 效果、实现难度和维护成本之间达到了最佳平衡。

三、hreflang 配置详解

3.1 hreflang 标签的作用

hreflang 标签告诉搜索引擎:

  1. 这个页面有哪些语言版本
  2. 每个版本的完整 URL 是什么
  3. 每个版本对应的语言和地区代码

3.2 在 Next.js App Router 中配置 hreflang

方法 1:使用 Metadata API(推荐)

Next.js 13+ 的 App Router 提供了更简洁的 Metadata API:

// app/[lang]/about/page.tsx
import { Metadata } from 'next'

type Props = {
  params: { lang: string }
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { lang } = params

  // 定义支持的语言列表
  const languages = ['en', 'zh', 'ja', 'de']

  // 生成所有语言的 alternate 链接
  const alternates = {
    canonical: `https://example.com/${lang}/about`,
    languages: languages.reduce((acc, locale) => {
      acc[locale] = `https://example.com/${locale}/about`
      return acc
    }, {} as Record<string, string>)
  }

  return {
    title: 'About Us',
    alternates,
    // 添加 x-default 用于未匹配的语言
    other: {
      'x-default': 'https://example.com/en/about'
    }
  }
}

export default function AboutPage({ params }: Props) {
  return <div>About page in {params.lang}</div>
}

方法 2:使用自定义 Head 组件

适用于需要更多自定义控制的场景:

// components/I18nHead.tsx
import Head from 'next/head'

interface I18nHeadProps {
  currentLang: string
  pathname: string
  languages?: string[]
}

export default function I18nHead({
  currentLang,
  pathname,
  languages = ['en', 'zh', 'ja', 'de']
}: I18nHeadProps) {
  const baseUrl = 'https://example.com'

  return (
    <Head>
      {/* 当前页面的 canonical URL */}
      <link rel="canonical" href={`${baseUrl}/${currentLang}${pathname}`} />

      {/* 所有语言版本的 hreflang */}
      {languages.map(lang => (
        <link
          key={lang}
          rel="alternate"
          hrefLang={lang}
          href={`${baseUrl}/${lang}${pathname}`}
        />
      ))}

      {/* x-default 指向默认语言 */}
      <link
        rel="alternate"
        hrefLang="x-default"
        href={`${baseUrl}/en${pathname}`}
      />
    </Head>
  )
}

使用方式:

// app/[lang]/about/page.tsx
import I18nHead from '@/components/I18nHead'

export default function AboutPage({ params }: { params: { lang: string } }) {
  return (
    <>
      <I18nHead
        currentLang={params.lang}
        pathname="/about"
      />
      <div>About page content</div>
    </>
  )
}

3.3 在 Next.js Pages Router 中配置 hreflang

Pages Router 使用不同的 API:

// pages/about.tsx
import { GetStaticProps } from 'next'
import Head from 'next/head'
import { useRouter } from 'next/router'

export default function AboutPage() {
  const router = useRouter()
  const { locale, locales, asPath } = router
  const baseUrl = 'https://example.com'

  return (
    <>
      <Head>
        {/* 当前页面的 canonical URL */}
        <link rel="canonical" href={`${baseUrl}/${locale}${asPath}`} />

        {/* 所有语言版本的 hreflang */}
        {locales?.map(loc => (
          <link
            key={loc}
            rel="alternate"
            hrefLang={loc}
            href={`${baseUrl}/${loc}${asPath}`}
          />
        ))}

        {/* x-default 默认语言 */}
        <link
          rel="alternate"
          hrefLang="x-default"
          href={`${baseUrl}/en${asPath}`}
        />
      </Head>

      <div>About page content</div>
    </>
  )
}

export const getStaticProps: GetStaticProps = async ({ locale }) => {
  return {
    props: {
      messages: (await import(`../locales/${locale}.json`)).default
    }
  }
}

3.4 使用地区代码的高级配置

如果你的网站需要针对特定国家/地区提供定制化内容,可以使用 language-REGION 格式:

// 针对不同地区的英语用户
const hreflangConfig = {
  'en-US': 'https://example.com/en-us/about', // 美国英语
  'en-GB': 'https://example.com/en-gb/about', // 英国英语
  'en-AU': 'https://example.com/en-au/about', // 澳大利亚英语
  'zh-CN': 'https://example.com/zh-cn/about', // 中国大陆简体中文
  'zh-TW': 'https://example.com/zh-tw/about', // 台湾繁体中文
  'zh-HK': 'https://example.com/zh-hk/about', // 香港繁体中文
}

在 Next.js 中实现地区级别的路由:

// next.config.js
module.exports = {
  i18n: {
    locales: ['en-US', 'en-GB', 'en-AU', 'zh-CN', 'zh-TW', 'zh-HK'],
    defaultLocale: 'en-US',
  }
}

3.5 常见配置错误和修复方案

错误 1:缺少自引用

<!-- ❌ 错误:当前页面没有引用自己 -->
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />

<!-- ✅ 正确:必须包含当前页面的自引用 -->
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />

为什么必须自引用?
Google 要求 hreflang 必须是对称的,每个语言版本都必须引用所有其他版本,包括自己。

错误 2:缺少 x-default

<!-- ✅ 推荐:添加 x-default 作为默认语言 -->
<link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />

x-default 的作用是为未匹配任何语言的用户提供默认版本。例如:

  • 用户使用阿拉伯语浏览器,但你的网站不支持阿拉伯语
  • 搜索引擎会自动返回 x-default 指定的页面

错误 3:hreflang 和 canonical 冲突

<!-- ❌ 错误:canonical 指向了其他语言版本 -->
<link rel="canonical" href="https://example.com/en/about" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />

<!-- ✅ 正确:canonical 应该指向当前语言版本 -->
<link rel="canonical" href="https://example.com/zh/about" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />

关键原则:canonical 标签必须指向当前页面本身的 URL,不能指向其他语言版本。

四、多语言 Sitemap 实现

Sitemap 是帮助搜索引擎发现和索引你的页面的重要工具。对于多语言网站,正确的 Sitemap 配置至关重要。

4.1 为什么需要多语言 Sitemap

多语言 Sitemap 的三大好处:

  1. 加快索引速度 - 主动告诉搜索引擎所有语言版本的页面,无需被动等待爬虫发现
  2. 确保完整性 - 避免某些语言版本被遗漏,尤其是链接层级较深的页面
  3. 传递 hreflang 信息 - 在 Sitemap 中也可以配置 hreflang,强化语言关系

4.2 Sitemap 策略选择

根据网站规模选择合适的方案:

方案 1:单一 Sitemap(推荐用于小型网站)

所有语言的 URL 放在一个 sitemap.xml 中,适合页面数量在 5000 以内的网站:

<?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/en/about</loc>
    <xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about"/>
    <xhtml:link rel="alternate" hreflang="zh" href="https://example.com/zh/about"/>
    <xhtml:link rel="alternate" hreflang="ja" href="https://example.com/ja/about"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/about"/>
  </url>
  <!-- 中文版本 -->
  <url>
    <loc>https://example.com/zh/about</loc>
    <xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about"/>
    <xhtml:link rel="alternate" hreflang="zh" href="https://example.com/zh/about"/>
    <xhtml:link rel="alternate" hreflang="ja" href="https://example.com/ja/about"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/about"/>
  </url>
</urlset>

方案 2:分语言 Sitemap(推荐用于大型网站)

每种语言一个独立的 Sitemap,然后用 sitemap index 聚合,适合页面数量超过 5000 或语言版本较多的网站:

<!-- sitemap-index.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>https://example.com/sitemap-en.xml</loc>
    <lastmod>2025-01-01</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://example.com/sitemap-zh.xml</loc>
    <lastmod>2025-01-01</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://example.com/sitemap-ja.xml</loc>
    <lastmod>2025-01-01</lastmod>
  </sitemap>
</sitemapindex>

4.3 Next.js App Router 中生成 Sitemap

Next.js 13+ 提供了内置的 sitemap 生成功能,非常方便:

// app/sitemap.ts
import { MetadataRoute } from 'next'

// 定义支持的语言列表
const languages = ['en', 'zh', 'ja', 'de']

// 定义网站的所有路由(不包含语言前缀)
const routes = ['', '/about', '/blog', '/contact']

export default function sitemap(): MetadataRoute.Sitemap {
  const baseUrl = 'https://example.com'
  const sitemap: MetadataRoute.Sitemap = []

  // 为每个路由生成所有语言版本
  routes.forEach(route => {
    // 为每种语言创建一个条目
    languages.forEach(lang => {
      const url = `${baseUrl}/${lang}${route}`

      sitemap.push({
        url,
        lastModified: new Date(),
        changeFrequency: 'weekly',
        priority: route === '' ? 1 : 0.8,
        // Next.js 会自动处理 alternateRefs
        alternates: {
          languages: languages.reduce((acc, l) => {
            acc[l] = `${baseUrl}/${l}${route}`
            return acc
          }, {} as Record<string, string>)
        }
      })
    })
  })

  return sitemap
}

4.4 动态内容的 Sitemap 生成

如果你的网站有动态内容(如博客文章、产品页面),需要从数据库或 CMS 获取数据:

// app/sitemap.ts
import { MetadataRoute } from 'next'

const languages = ['en', 'zh', 'ja']
const baseUrl = 'https://example.com'

// 从数据库或 CMS 获取文章列表
async function getArticles() {
  // 实际项目中,这里应该从数据库或 CMS API 获取
  // 例如:const articles = await prisma.article.findMany()
  return [
    { slug: 'getting-started', lastModified: '2025-01-01' },
    { slug: 'advanced-guide', lastModified: '2025-01-15' },
  ]
}

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const sitemap: MetadataRoute.Sitemap = []

  // 1. 添加静态页面
  const staticPages = ['', '/about', '/contact']
  staticPages.forEach(page => {
    languages.forEach(lang => {
      sitemap.push({
        url: `${baseUrl}/${lang}${page}`,
        lastModified: new Date(),
        changeFrequency: 'monthly',
        priority: page === '' ? 1 : 0.8,
        alternates: {
          languages: languages.reduce((acc, l) => {
            acc[l] = `${baseUrl}/${l}${page}`
            return acc
          }, {} as Record<string, string>)
        }
      })
    })
  })

  // 2. 添加动态内容(博客文章)
  const articles = await getArticles()
  articles.forEach(article => {
    languages.forEach(lang => {
      sitemap.push({
        url: `${baseUrl}/${lang}/blog/${article.slug}`,
        lastModified: new Date(article.lastModified),
        changeFrequency: 'weekly',
        priority: 0.6,
        alternates: {
          languages: languages.reduce((acc, l) => {
            acc[l] = `${baseUrl}/${l}/blog/${article.slug}`
            return acc
          }, {} as Record<string, string>)
        }
      })
    })
  })

  return sitemap
}

4.5 Pages Router 中生成 Sitemap

对于 Pages Router,需要手动创建 API 路由:

// pages/api/sitemap.xml.ts
import { NextApiRequest, NextApiResponse } from 'next'

const baseUrl = 'https://example.com'
const languages = ['en', 'zh', 'ja']

function generateSiteMap(pages: string[]) {
  return `<?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">
${pages.map(page => {
  return languages.map(lang => {
    const url = `${baseUrl}/${lang}${page}`
    const alternates = languages.map(l =>
      `    <xhtml:link rel="alternate" hreflang="${l}" href="${baseUrl}/${l}${page}"/>`
    ).join('\n')

    return `  <url>
    <loc>${url}</loc>
    <lastmod>${new Date().toISOString()}</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
${alternates}
    <xhtml:link rel="alternate" hreflang="x-default" href="${baseUrl}/en${page}"/>
  </url>`
  }).join('\n')
}).join('\n')}
</urlset>`
}

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  // 定义所有页面路由
  const pages = ['', '/about', '/blog', '/contact']

  const sitemap = generateSiteMap(pages)

  res.setHeader('Content-Type', 'text/xml')
  res.write(sitemap)
  res.end()
}

4.6 提交 Sitemap 到搜索引擎

生成 Sitemap 后,需要主动提交给搜索引擎,加快索引速度。

方法 1:在 robots.txt 中声明

这是最简单的方法,搜索引擎会自动读取:

# public/robots.txt
User-agent: *
Allow: /

Sitemap: https://example.com/sitemap.xml

方法 2:提交到 Google Search Console

手动提交可以立即触发索引:

  1. 访问 Google Search Console
  2. 选择你的网站属性
  3. 左侧菜单选择”站点地图”
  4. 输入 sitemap.xml 的 URL
  5. 点击”提交”

方法 3:提交到 Bing Webmaster Tools

别忘了 Bing 也有一定的市场份额:

  1. 访问 Bing Webmaster Tools
  2. 添加你的网站
  3. 在”站点地图”部分提交 sitemap.xml

4.7 验证 Sitemap

使用以下工具验证你的 Sitemap 格式是否正确:

  1. XML Sitemap Validator: https://www.xml-sitemaps.com/validate-xml-sitemap.html
  2. Google Search Console: 提交后可以查看索引状态和错误提示
  3. 在线 XML 验证器: 确保 XML 格式完全符合标准

五、最佳实践和注意事项

5.1 内容翻译质量的重要性

搜索引擎(尤其是 Google)具备检测低质量翻译的能力,这会直接影响你的排名。

不要做:

  • ❌ 使用 Google Translate 等自动翻译工具直接生成内容
  • ❌ 仅翻译导航和标题,正文内容保持相同语言
  • ❌ 不同语言版本的内容结构和信息量差异过大

应该做:

  • ✅ 聘请专业翻译或母语者进行翻译
  • ✅ 本地化内容而非仅翻译(考虑文化差异、表达习惯)
  • ✅ 保持各语言版本的内容一致性和质量标准

5.2 避免自动翻译的 SEO 风险

客户端自动翻译对 SEO 完全无效,因为搜索引擎爬虫看到的是原始内容:

// ❌ 不推荐:客户端自动翻译(搜索引擎无法索引)
import GoogleTranslate from 'google-translate-api'

export default function Page() {
  const [content, setContent] = useState('')

  useEffect(() => {
    // 这种方式对 SEO 无效
    GoogleTranslate(originalText, { to: 'zh' })
      .then(res => setContent(res.text))
  }, [])

  return <div>{content}</div>
}
// ✅ 推荐:服务端渲染真实翻译内容
export default function Page({ params }: { params: { lang: string } }) {
  // 从数据库或文件系统获取真实翻译内容
  const content = await getTranslatedContent(params.lang)

  return <div>{content}</div>
}

5.3 性能优化建议

多语言网站往往面向全球用户,性能优化尤为重要。

1. 使用 CDN 加速多地区访问

// next.config.js
module.exports = {
  images: {
    domains: ['cdn.example.com'],
  },
  // 启用自动压缩
  compress: true,
}

2. 按需加载语言包

避免一次性加载所有语言的翻译文件:

// 动态导入当前语言的翻译文件
const messages = await import(`@/locales/${lang}.json`)

3. 缓存策略

合理设置页面缓存时间:

// app/[lang]/layout.tsx
export const revalidate = 3600 // 1小时重新验证一次

5.4 监控和维护

多语言 SEO 不是一次性任务,需要持续监控和优化。

1. 定期检查 hreflang 错误

使用 Google Search Console 的”国际定位”报告:

  • 检查 hreflang 标签错误和警告
  • 查看各语言版本的索引状态
  • 监控不同语言版本的搜索表现和点击率

2. 监控工具推荐

3. 创建监控脚本

自动化检查 hreflang 配置:

// scripts/check-hreflang.ts
import { JSDOM } from 'jsdom'

async function checkHreflang(url: string) {
  const response = await fetch(url)
  const html = await response.text()
  const dom = new JSDOM(html)
  const document = dom.window.document

  const hreflangLinks = document.querySelectorAll('link[rel="alternate"][hreflang]')

  console.log(`Found ${hreflangLinks.length} hreflang links on ${url}`)

  hreflangLinks.forEach(link => {
    const hreflang = link.getAttribute('hreflang')
    const href = link.getAttribute('href')
    console.log(`  ${hreflang}: ${href}`)
  })

  // 检查是否有自引用
  const currentUrl = new URL(url).href
  const hasSelfReference = Array.from(hreflangLinks).some(
    link => link.getAttribute('href') === currentUrl
  )

  if (!hasSelfReference) {
    console.warn('⚠️ Warning: Missing self-reference hreflang tag')
  }

  // 检查是否有 x-default
  const hasXDefault = Array.from(hreflangLinks).some(
    link => link.getAttribute('hreflang') === 'x-default'
  )

  if (!hasXDefault) {
    console.warn('⚠️ Warning: Missing x-default hreflang tag')
  }
}

// 使用示例
checkHreflang('https://example.com/en/about')
checkHreflang('https://example.com/zh/about')

六、实战案例:完整的项目示例

让我们通过一个完整的示例,展示如何在 Next.js App Router 项目中实现多语言 SEO。

6.1 项目结构

my-i18n-site/
├── app/
│   ├── [lang]/                    # 动态语言路由
│   │   ├── layout.tsx             # 语言级别的布局
│   │   ├── page.tsx               # 首页
│   │   ├── about/
│   │   │   └── page.tsx           # 关于页面
│   │   └── blog/
│   │       ├── page.tsx           # 博客列表
│   │       └── [slug]/
│   │           └── page.tsx       # 博客文章详情
│   ├── sitemap.ts                 # Sitemap 生成器
│   └── robots.ts                  # Robots.txt 生成器
├── components/
│   └── I18nMetadata.tsx           # 多语言元数据组件
├── lib/
│   ├── i18n.ts                    # 国际化配置
│   └── articles.ts                # 文章数据获取
├── locales/                       # 翻译文件
│   ├── en.json
│   ├── zh.json
│   └── ja.json
└── next.config.js                 # Next.js 配置

6.2 配置文件

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 注意:App Router 不使用 i18n 配置
  // 需要手动实现语言路由
}

module.exports = nextConfig
// lib/i18n.ts
export const languages = ['en', 'zh', 'ja'] as const
export type Language = (typeof languages)[number]

export const defaultLanguage: Language = 'en'

export const languageNames: Record<Language, string> = {
  en: 'English',
  zh: '中文',
  ja: '日本語',
}

export function isValidLanguage(lang: string): lang is Language {
  return languages.includes(lang as Language)
}

6.3 Layout 组件

// app/[lang]/layout.tsx
import { languages, isValidLanguage } from '@/lib/i18n'
import { notFound } from 'next/navigation'

export async function generateStaticParams() {
  return languages.map(lang => ({ lang }))
}

export default function LangLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: { lang: string }
}) {
  // 验证语言代码是否有效
  if (!isValidLanguage(params.lang)) {
    notFound()
  }

  return (
    <html lang={params.lang}>
      <body>{children}</body>
    </html>
  )
}

6.4 页面组件带 Metadata

// app/[lang]/about/page.tsx
import { Metadata } from 'next'
import { languages, Language } from '@/lib/i18n'

type Props = {
  params: { lang: Language }
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { lang } = params
  const baseUrl = 'https://example.com'
  const pathname = '/about'

  // 生成 alternates 配置
  const alternates = {
    canonical: `${baseUrl}/${lang}${pathname}`,
    languages: languages.reduce((acc, locale) => {
      acc[locale] = `${baseUrl}/${locale}${pathname}`
      return acc
    }, {} as Record<string, string>)
  }

  // 根据语言返回不同的标题和描述
  const titles: Record<Language, string> = {
    en: 'About Us - Learn More About Our Company',
    zh: '关于我们 - 了解更多关于我们公司的信息',
    ja: '私たちについて - 当社についてもっと知る',
  }

  const descriptions: Record<Language, string> = {
    en: 'Learn about our mission, values, and the team behind our success.',
    zh: '了解我们的使命、价值观以及我们成功背后的团队。',
    ja: '私たちの使命、価値観、そして成功を支えるチームについて学びます。',
  }

  return {
    title: titles[lang],
    description: descriptions[lang],
    alternates,
    openGraph: {
      title: titles[lang],
      description: descriptions[lang],
      url: `${baseUrl}/${lang}${pathname}`,
      siteName: 'Example Site',
      locale: lang,
      type: 'website',
    },
  }
}

export default function AboutPage({ params }: Props) {
  const content = {
    en: 'About us content in English...',
    zh: '关于我们的中文内容...',
    ja: '私たちについての日本語コンテンツ...',
  }

  return (
    <div>
      <h1>About Us</h1>
      <p>{content[params.lang]}</p>
    </div>
  )
}

6.5 动态路由带 hreflang

// app/[lang]/blog/[slug]/page.tsx
import { Metadata } from 'next'
import { languages, Language } from '@/lib/i18n'
import { getArticle, getAllArticles } from '@/lib/articles'
import { notFound } from 'next/navigation'

type Props = {
  params: { lang: Language; slug: string }
}

// 静态生成所有文章页面
export async function generateStaticParams() {
  const articles = await getAllArticles()

  return languages.flatMap(lang =>
    articles.map(article => ({
      lang,
      slug: article.slug,
    }))
  )
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { lang, slug } = params
  const article = await getArticle(slug, lang)

  if (!article) {
    return {}
  }

  const baseUrl = 'https://example.com'
  const pathname = `/blog/${slug}`

  const alternates = {
    canonical: `${baseUrl}/${lang}${pathname}`,
    languages: languages.reduce((acc, locale) => {
      acc[locale] = `${baseUrl}/${locale}${pathname}`
      return acc
    }, {} as Record<string, string>)
  }

  return {
    title: article.title,
    description: article.excerpt,
    alternates,
    openGraph: {
      title: article.title,
      description: article.excerpt,
      url: `${baseUrl}/${lang}${pathname}`,
      type: 'article',
      publishedTime: article.publishedAt,
      authors: [article.author],
    },
  }
}

export default async function BlogArticle({ params }: Props) {
  const { lang, slug } = params
  const article = await getArticle(slug, lang)

  if (!article) {
    notFound()
  }

  return (
    <article>
      <h1>{article.title}</h1>
      <p>{article.content}</p>
    </article>
  )
}

6.6 Sitemap 生成

// app/sitemap.ts
import { MetadataRoute } from 'next'
import { languages } from '@/lib/i18n'
import { getAllArticles } from '@/lib/articles'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://example.com'
  const sitemap: MetadataRoute.Sitemap = []

  // 1. 添加静态页面
  const staticPages = ['', '/about', '/contact']
  staticPages.forEach(page => {
    languages.forEach(lang => {
      sitemap.push({
        url: `${baseUrl}/${lang}${page}`,
        lastModified: new Date(),
        changeFrequency: 'monthly',
        priority: page === '' ? 1 : 0.8,
        alternates: {
          languages: languages.reduce((acc, l) => {
            acc[l] = `${baseUrl}/${l}${page}`
            return acc
          }, {} as Record<string, string>)
        }
      })
    })
  })

  // 2. 添加动态内容(博客文章)
  const articles = await getAllArticles()
  articles.forEach(article => {
    languages.forEach(lang => {
      sitemap.push({
        url: `${baseUrl}/${lang}/blog/${article.slug}`,
        lastModified: new Date(article.updatedAt),
        changeFrequency: 'weekly',
        priority: 0.6,
        alternates: {
          languages: languages.reduce((acc, l) => {
            acc[l] = `${baseUrl}/${l}/blog/${article.slug}`
            return acc
          }, {} as Record<string, string>)
        }
      })
    })
  })

  return sitemap
}

6.7 Robots.txt

// app/robots.ts
import { MetadataRoute } from 'next'

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: '*',
      allow: '/',
    },
    sitemap: 'https://example.com/sitemap.xml',
  }
}

七、验证和测试

7.1 本地测试清单

在部署到生产环境之前,请完成以下检查:

  • 所有页面的 <html> 标签都有正确的 lang 属性
  • 每个页面都包含完整的 hreflang 标签(包括所有语言版本)
  • hreflang 标签包含自引用(当前页面引用自己)
  • 存在 x-default 标签指向默认语言
  • canonical 标签指向正确的 URL(当前语言版本)
  • Sitemap 包含所有语言版本的 URL
  • robots.txt 正确指向 sitemap.xml
  • 不同语言版本的内容质量一致、翻译准确

7.2 使用 Google Rich Results Test

访问 Google Rich Results Test 测试你的页面:

  1. 输入页面 URL
  2. 等待 Google 抓取和分析
  3. 检查是否有错误或警告
  4. 查看 hreflang 标签是否被正确识别

7.3 使用 hreflang 检查工具

专门的 hreflang 验证工具:

这些工具可以:

  • 批量检查多个页面的 hreflang 配置
  • 发现对称性问题(单向引用)
  • 检测语言代码错误

7.4 Google Search Console 验证

部署到生产环境后:

  1. 提交 Sitemap 到 Google Search Console
  2. 等待 1-2 周让 Google 完成初步索引
  3. 检查”国际定位” > “语言”报告
  4. 查看是否有 hreflang 错误和警告
  5. 监控各语言版本的搜索表现

八、常见问题解答

Q1: hreflang 和 canonical 有什么区别?

  • canonical - 告诉搜索引擎这个页面的规范 URL,用于处理重复内容问题
  • hreflang - 告诉搜索引擎这个页面有哪些语言版本,用于语言定位

它们可以同时使用,互不冲突。每个语言版本的 canonical 应该指向自己,hreflang 指向所有语言版本。

Q2: 必须为每个页面都配置 hreflang 吗?

是的。hreflang 标签必须在每个语言版本中都存在,并且必须是对称的(互相引用)。如果只在英文页面配置而中文页面没有,Google 会忽略这些配置。

Q3: x-default 应该指向哪个语言?

通常指向你的默认语言或最通用的版本。推荐策略:

  • 如果主要受众是英语用户,指向英语版本
  • 如果是全球性网站,指向国际英语版本(en-US)
  • 如果是特定区域网站,指向该区域的主要语言

Q4: 子目录 vs 子域名,哪个更好?

子目录(推荐)

  • SEO 权重集中在主域名
  • 实现和维护简单
  • 适合大多数项目

子域名

  • 可以独立部署到不同服务器
  • 适合大型国际网站,每个市场需要独立运营
  • 需要额外的域名管理和成本

结论:除非有特殊需求,否则选择子目录策略。

Q5: 如何处理机器翻译的内容?

不推荐在 SEO 中使用机器翻译:

  • 搜索引擎能识别低质量翻译,影响排名
  • 可能被视为低价值内容
  • 用户体验差,跳出率高

如果预算有限的应对策略:

  1. 优先翻译核心页面(首页、主要产品页、高流量页面)
  2. 使用机器翻译后,必须由人工校对和润色
  3. 逐步改进翻译质量,定期更新内容

Q6: 多语言网站需要多久才能被索引?

一般时间线:

  • 提交 Sitemap 后 1-2 周开始索引
  • 完全索引可能需要 1-2 个月
  • SEO 权重积累需要 3-6 个月

加快索引的方法:

  • 确保 Sitemap 格式正确并及时提交
  • 提高内容质量和更新频率
  • 获取高质量的外部链接
  • 在 Google Search Console 中请求索引(针对重要页面)

九、总结

多语言 SEO 优化是国际化网站成功的关键。让我们回顾核心要点:

9.1 核心要点

  1. URL 策略

    • 推荐使用子目录策略(example.com/en/、example.com/zh/)
    • 确保 URL 结构清晰、一致、易于理解
  2. hreflang 配置

    • 每个页面必须配置完整的 hreflang 标签(包括所有语言版本)
    • 必须包含自引用(当前页面引用自己)
    • 添加 x-default 指向默认语言
    • 使用正确的语言代码(遵循 ISO 639-1 标准)
  3. Sitemap

    • 包含所有语言版本的 URL
    • 在 Sitemap 中也添加 hreflang 信息(可选但推荐)
    • 定期更新并提交到搜索引擎
  4. 内容质量

    • 避免使用机器翻译直接发布
    • 保持各语言版本的内容一致性和专业性
    • 本地化而非仅翻译(考虑文化和表达习惯)
  5. 监控和维护

    • 使用 Google Search Console 持续监控
    • 定期检查 hreflang 错误和警告
    • 追踪各语言版本的搜索表现和转化率

9.2 行动清单

完成以下步骤,确保你的多语言 SEO 配置正确:

  • 选择并实现 URL 策略(子目录推荐)
  • 在所有页面添加完整的 hreflang 标签
  • 配置正确的 canonical 标签
  • 生成包含所有语言版本的 Sitemap
  • 配置 robots.txt 并指向 sitemap.xml
  • 提交 Sitemap 到 Google Search Console 和 Bing Webmaster Tools
  • 使用验证工具检查 hreflang 配置
  • 检查和提升内容翻译质量
  • 设置监控流程和定期检查机制

9.3 延伸阅读

正确实现多语言 SEO 需要时间和精力,但回报是巨大的:更好的搜索排名、更精准的用户匹配、更高的转化率。遵循本文的最佳实践,你的多语言网站将在搜索引擎中获得更好的表现。

Next.js多语言SEO完整配置流程

从配置hreflang标签到生成多语言Sitemap、选择URL策略的完整步骤

⏱️ 预计耗时: 2 小时

  1. 1

    步骤1: 配置hreflang标签

    在metadata中配置:
    ```tsx
    // app/[locale]/about/page.tsx
    export async function generateMetadata({ params }): Promise<Metadata> {
    const { locale } = params

    return {
    title: 'About Us',
    alternates: {
    languages: {
    'zh': '/zh/about',
    'en': '/en/about',
    'x-default': '/en/about', // 默认语言
    },
    },
    }
    }
    ```

    关键点:
    • 包含所有语言版本
    • x-default指向默认语言
    • 每个页面都要配置

    HTML输出:
    ```html
    <link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
    <link rel="alternate" hreflang="en" href="https://example.com/en/about" />
    <link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />
    ```

    作用:
    • 告诉搜索引擎页面的目标语言
    • 防止内容重复惩罚
    • 精准匹配用户
  2. 2

    步骤2: 生成多语言Sitemap

    方法1:为每种语言生成独立Sitemap
    ```tsx
    // app/[locale]/sitemap.ts
    export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
    const baseUrl = 'https://example.com'
    const locale = params.locale

    return [
    {
    url: `${baseUrl}/${locale}`,
    lastModified: new Date(),
    changeFrequency: 'daily',
    priority: 1,
    },
    // ...
    ]
    }
    ```

    方法2:使用Sitemap索引
    ```tsx
    // app/sitemap.ts
    export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
    const locales = ['zh', 'en']
    const baseUrl = 'https://example.com'

    return locales.flatMap(locale => [
    {
    url: `${baseUrl}/${locale}`,
    lastModified: new Date(),
    changeFrequency: 'daily',
    priority: 1,
    },
    // ...
    ])
    }
    ```

    关键点:
    • 包含所有语言版本
    • 使用正确的URL格式
    • 提交到Google Search Console
  3. 3

    步骤3: 选择URL策略

    方案1:子路径(推荐)
    • URL格式:/zh/about、/en/about
    • 配置简单
    • SEO友好
    • 适合大多数项目

    方案2:域名
    • URL格式:zh.example.com、en.example.com
    • 需要配置多个域名
    • 更专业
    • 适合大型项目

    方案3:Cookie
    • 通过Cookie切换语言
    • URL不包含语言前缀
    • SEO不友好
    • 不推荐

    选择建议:
    • 大多数项目 → 子路径
    • 大型项目 → 域名
    • 避免 → Cookie

    关键点:子路径方案SEO最友好,推荐使用。
  4. 4

    步骤4: 验证和测试

    验证工具:

    1. Google Search Console:
    • 提交多语言Sitemap
    • 检查hreflang标签
    • 查看索引状态

    2. hreflang测试工具:
    • https://www.aleydasolis.com/en/english-tools/international-seo-tools/hreflang-tags-validator/
    • 检查hreflang配置是否正确

    3. 多语言Sitemap验证:
    • 检查Sitemap格式
    • 确认包含所有语言版本
    • 验证URL正确性

    常见错误检查:
    • hreflang标签缺失
    • x-default配置错误
    • Sitemap不包含所有语言版本
    • URL格式不一致

    建议:配置后立即验证,不要等到问题出现。

常见问题

什么是hreflang标签?为什么需要它?
hreflang是一个HTML属性,用于告诉搜索引擎页面的目标语言和地区。

主要作用:
1. 防止内容重复惩罚 - 告诉搜索引擎不同语言版本是同一内容的翻译,而非重复内容
2. 精准匹配用户 - 根据用户的语言和地区设置,显示最合适的页面版本
3. 提升用户体验 - 避免用户看到错误语言版本的内容

配置方法:
```tsx
export async function generateMetadata({ params }): Promise<Metadata> {
return {
alternates: {
languages: {
'zh': '/zh/about',
'en': '/en/about',
'x-default': '/en/about',
},
},
}
}
```

关键点:
• 包含所有语言版本
• x-default指向默认语言
• 每个页面都要配置

根据Google的统计数据,超过60%的多语言网站存在hreflang配置错误。
如何配置hreflang标签?
在metadata中配置:
```tsx
// app/[locale]/about/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
const { locale } = params

return {
title: 'About Us',
alternates: {
languages: {
'zh': '/zh/about',
'en': '/en/about',
'x-default': '/en/about', // 默认语言
},
},
}
}
```

HTML输出:
```html
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />
```

关键点:
• 包含所有语言版本
• x-default指向默认语言
• 每个页面都要配置
• URL必须是绝对路径

注意:hreflang标签必须包含所有语言版本,包括当前页面。
如何生成多语言Sitemap?
方法1:为每种语言生成独立Sitemap
```tsx
// app/[locale]/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://example.com'
const locale = params.locale

return [
{
url: `${baseUrl}/${locale}`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
]
}
```

方法2:使用Sitemap索引
```tsx
// app/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const locales = ['zh', 'en']
const baseUrl = 'https://example.com'

return locales.flatMap(locale => [
{
url: `${baseUrl}/${locale}`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
])
}
```

关键点:
• 包含所有语言版本
• 使用正确的URL格式
• 提交到Google Search Console

建议:使用Sitemap索引,更灵活。
多语言网站的URL策略怎么选择?
三种URL策略:

方案1:子路径(推荐)
• URL格式:/zh/about、/en/about
• 配置简单
• SEO友好
• 适合大多数项目

方案2:域名
• URL格式:zh.example.com、en.example.com
• 需要配置多个域名
• 更专业
• 适合大型项目

方案3:Cookie
• 通过Cookie切换语言
• URL不包含语言前缀
• SEO不友好
• 不推荐

选择建议:
• 大多数项目 → 子路径
• 大型项目 → 域名
• 避免 → Cookie

关键点:子路径方案SEO最友好,推荐使用。

注意:选择URL策略后,hreflang标签的URL格式要一致。
如何验证多语言SEO配置?
验证工具:

1. Google Search Console:
• 提交多语言Sitemap
• 检查hreflang标签
• 查看索引状态

2. hreflang测试工具:
• https://www.aleydasolis.com/en/english-tools/international-seo-tools/hreflang-tags-validator/
• 检查hreflang配置是否正确

3. 多语言Sitemap验证:
• 检查Sitemap格式
• 确认包含所有语言版本
• 验证URL正确性

常见错误检查:
• hreflang标签缺失
• x-default配置错误
• Sitemap不包含所有语言版本
• URL格式不一致

建议:
• 配置后立即验证
• 定期检查索引状态
• 及时修复问题

记住:验证是SEO优化的重要环节,不要忽略。
多语言SEO的常见错误有哪些?
常见错误:

1. 缺少hreflang标签
• 搜索引擎不知道页面语言
• 可能显示错误的语言版本

2. x-default配置错误
• 没有配置x-default
• 或x-default指向错误的语言

3. Sitemap不包含所有语言版本
• 只提交了部分语言版本
• 搜索引擎无法发现所有页面

4. URL格式不一致
• hreflang标签的URL格式不一致
• 导致配置错误

5. 重复内容问题
• 没有正确配置hreflang
• 搜索引擎认为不同语言版本是重复内容

解决方法:
• 配置hreflang标签
• 包含所有语言版本
• 使用正确的URL格式
• 提交完整的Sitemap

建议:遵循本文的最佳实践,避免这些常见错误。

如果你在实施过程中遇到任何问题,欢迎在评论区讨论交流!

16 分钟阅读 · 发布于: 2025年12月25日 · 修改于: 2026年1月22日

评论

使用 GitHub 账号登录后即可评论

相关文章