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-intl、react-intl、i18next等负责翻译层
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 里配置:
localesdefaultLocale- domain routing
- automatic locale detection
这套能力在存量项目里仍然很常见。
Pages Router 和 App Router 在国际化上的差异
Pages Router
更偏配置驱动,很多事情通过 next.config.js 的 i18n 配置来接。
App Router
更偏文件系统和路由段组织,locale 常直接成为 URL 段的一部分。
所以如果项目还在 Pages Router,需要会读内建 i18n 路由;
如果项目已经在 App Router,通常更值得优先掌握 [lang] 目录模型。
sub-path 和 domain routing 怎么选
sub-path
例如:
/en/products/zh-CN/products
优点:
- 部署简单
- 结构直观
- 单域名管理方便
domain routing
例如:
example.comexample.cnexample.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 分资源。