跳到主要内容

Next.js 国际化、多语言与路由策略

国际化这件事在 Next.js 里通常分成两层:

  • Internationalization:路由、语言、地区、格式化策略
  • Localization:具体文案翻译和内容落地

Next.js 主要负责前一层,也就是:

  • 路由怎么带 locale
  • 默认语言怎么选
  • 用户访问时该落到哪个语言路径
  • 多语言页面的渲染和组织怎么接起来

至于具体文案翻译,往往还要结合 i18n 库或业务侧内容系统。

先看 App Router 里的主线

当前官方 App Router 文档更推荐的理解方式是:

  • 用 URL 层表示 locale
  • Accept-Language 辅助做首次语言选择
  • 在路由层组织不同 locale 的页面结构

也就是说,多语言不是“页面里切换几段文案”那么简单,它首先是一套路由策略。

最常见的一种目录结构

app/
[lang]/
layout.tsx
page.tsx
products/
page.tsx

这样可以得到:

  • /en
  • /zh-CN
  • /ja

[lang] 就是 locale 路由段。

为什么这套方式直观

因为:

  • locale 直接进入 URL
  • 服务端和客户端都容易识别当前语言
  • metadata、导航、内容查询、缓存标签都更容易按语言拆分

首次访问时怎么决定语言

官方文档里推荐参考浏览器的 Accept-Language 头。

常见做法是:

  • 定义一组支持的 locales
  • 设默认 locale
  • 首次访问根路径时,根据请求头判断跳到哪个 locale

例如可以在 proxy.ts 里做:

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

const locales = ['en-US', 'zh-CN'];
const defaultLocale = 'en-US';

function getLocale(request: NextRequest) {
const locale = request.headers.get('accept-language');
if (locale?.includes('zh')) return 'zh-CN';
return defaultLocale;
}

export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
const pathnameHasLocale = locales.some(
(locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`)
);

if (pathnameHasLocale) {
return NextResponse.next();
}

const locale = getLocale(request);
request.nextUrl.pathname = `/${locale}${pathname}`;
return NextResponse.redirect(request.nextUrl);
}

这个例子只是说明路由思路,实际项目里通常会配合更稳的 locale 匹配库。

Locale 和文案翻译不是一回事

这是最常见的误区之一。

Next.js 负责什么

  • URL 结构
  • locale 路由组织
  • 首次语言选择
  • 多语言页面渲染入口

翻译库负责什么

  • 词条字典
  • ICU message
  • 插值
  • 复数规则
  • 语言资源加载

所以现实项目里很常见的组合是:

  • Next 负责 locale 路由
  • next-intlreact-intli18next 等负责翻译层

App Router 下当前语言怎么往下传

比较常见的方式有两种:

1. 直接从路由段参数传下去

export default async function RootLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ lang: string }>;
}) {
const { lang } = await params;

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

2. 在服务端加载当前 locale 的 messages

然后通过 provider 注入客户端。

Pages Router 的内建 i18n 路由怎么理解

Pages Router 从 10 开始就有内建 i18n routing。

可以在 next.config 里配置:

  • locales
  • defaultLocale
  • domain routing
  • automatic locale detection

这套能力在存量项目里仍然很常见。

Pages Router 和 App Router 在国际化上的差异

Pages Router

更偏配置驱动,很多事情通过 next.config.jsi18n 配置来接。

App Router

更偏文件系统和路由段组织,locale 常直接成为 URL 段的一部分。

所以如果项目还在 Pages Router,需要会读内建 i18n 路由; 如果项目已经在 App Router,通常更值得优先掌握 [lang] 目录模型。

sub-path 和 domain routing 怎么选

sub-path

例如:

  • /en/products
  • /zh-CN/products

优点:

  • 部署简单
  • 结构直观
  • 单域名管理方便

domain routing

例如:

  • example.com
  • example.cn
  • example.jp

优点:

  • 本地化品牌感更强
  • 区域市场区分更清楚

代价:

  • 运维和部署复杂度更高
  • SEO 和证书、环境、回调域都更复杂

默认语言要不要带前缀

这其实就是一个很实际的路由策略问题。

默认语言不带前缀

例如:

  • /products
  • /zh-CN/products

优点:

  • 主市场 URL 更短

代价:

  • 多语言路径规则不完全对称

默认语言也带前缀

例如:

  • /en/products
  • /zh-CN/products

优点:

  • 规则统一
  • 更容易做工程一致性处理

代价:

  • 默认语言路径更长

这没有绝对标准,更重要的是全站保持一致。

SEO 上要额外注意什么

多语言不只是把文案翻译完,还要处理:

  • canonical
  • alternate language links
  • sitemap 按语言展开
  • robots 是否按区域或路径调整

如果这些没跟上,就会出现:

  • 多个语言页面互相竞争索引
  • 搜索引擎拿错主版本
  • 多语言内容抓取不完整

路由和缓存怎么受 locale 影响

语言一旦进 URL,就意味着:

  • 不同 locale 的页面通常是不同缓存键
  • metadata 也要按 locale 生成
  • sitemap 也要按 locale 构建
  • 数据获取和 CMS 查询也往往要带 locale 参数

所以国际化不是单独一层功能,而是会贯穿路由、缓存、SEO 和内容层。

最常见的几个误区

1. 只有翻译字典,没有 locale 路由策略

结果通常是:

  • URL 不稳定
  • SEO 混乱
  • 服务端难以判断当前语言

2. 把默认语言选择只放到客户端

这样会让首屏、SEO 和水合都变得不稳定。更稳的方式通常是让服务端在首次访问时就参与语言选择。

3. 把所有语言内容打进同一个大包

语言越多,这类问题越明显。更好的方式通常是按 locale 分资源。

4. 低估多语言 SEO 成本

多语言不是“翻完文案就结束”,路由和元信息要一起跟上。

一套比较稳的落地顺序

  1. 先定 locale 列表和默认语言
  2. 再定 URL 策略:sub-path 还是 domain
  3. 再把 locale 接进路由结构
  4. 再引入翻译层
  5. 最后补 canonical、alternate、sitemap 和区域级 SEO

推荐继续往下看

  1. Metadata、SEO、sitemap 与 robots
  2. 鉴权、权限与 Proxy
  3. 部署、自托管与 standalone

参考资料