当你的网站需要支持中英文切换时,URL 结构是第一个要解决的问题。我们在实际项目中选择了「默认语言无前缀,其他语言加前缀」的方案,并用 Next.js Middleware 实现了自动语言检测与路由。这篇文章分享具体的实现思路。
URL 结构设计
多语言 URL 通常有两种方案:
| 方案 | 中文 | 英文 |
|---|---|---|
| 所有语言加前缀 | /zh/blog | /en/blog |
| 默认语言无前缀 | /blog | /en/blog |
我们选择了第二种。原因很实际:网站主要面向中文用户,大部分流量不需要多一层 /zh 前缀,对 SEO 也更友好。
对应的 App Router 目录结构:
src/app/
├── (zh)/ # 路由组,中文页面(无前缀)
│ ├── blog/
│ └── page.tsx
├── en/ # 英文页面(/en 前缀)
│ ├── blog/
│ └── page.tsx
└── layout.tsx
(zh) 是 Next.js 的路由组语法,括号表示这个目录不会出现在 URL 中。
中间件的语言检测逻辑
Middleware 在每个请求到达页面之前执行,是做语言路由的理想位置。检测逻辑的优先级是:
- Cookie(用户手动切换过语言)
- Accept-Language 请求头(浏览器偏好)
- 默认语言(兜底为中文)
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
const defaultLocale = 'zh';
const locales = ['zh', 'en'];
function getLocale(request: NextRequest): string {
// 优先读 Cookie
const cookieLocale = request.cookies.get('locale')?.value;
if (cookieLocale && locales.includes(cookieLocale)) {
return cookieLocale;
}
// 其次读 Accept-Language
const acceptLang = request.headers.get('Accept-Language') || '';
for (const locale of locales) {
if (acceptLang.includes(locale)) {
return locale;
}
}
return defaultLocale;
}
路由重定向策略
检测到语言后,中间件决定是否需要重定向:
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// 跳过静态资源、API 路由、管理后台
if (
pathname.startsWith('/api') ||
pathname.startsWith('/_next') ||
pathname.includes('.')
) {
return NextResponse.next();
}
// 检查 URL 是否已包含语言前缀
const hasLocalePrefix = locales.some(
(l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`
);
if (hasLocalePrefix) return NextResponse.next();
// 无前缀的路径,检测语言并决定是否重定向
const locale = getLocale(request);
if (locale === defaultLocale) {
// 默认语言不加前缀,直接放行
return NextResponse.next();
}
// 非默认语言,重定向到带前缀的路径
const url = request.nextUrl.clone();
url.pathname = `/${locale}${pathname}`;
return NextResponse.redirect(url);
}
核心逻辑:中文用户访问 /blog 直接放行;英文用户访问 /blog 会被 302 重定向到 /en/blog。
语言切换的实现
用户手动切换语言时,前端做两件事:设置 Cookie 并跳转。
function switchLocale(newLocale: string) {
document.cookie = `locale=${newLocale}; path=/; max-age=31536000`;
const currentPath = window.location.pathname;
if (newLocale === 'zh') {
// 切到中文:去掉 /en 前缀
const path = currentPath.replace(/^\/en/, '') || '/';
window.location.href = path;
} else {
// 切到英文:加上 /en 前缀
const cleanPath = currentPath.replace(/^\/en/, '');
window.location.href = `/en${cleanPath}`;
}
}
Cookie 的 max-age 设为一年,下次访问时中间件优先读取 Cookie,不再依赖浏览器语言。
容易踩的坑
1. 静态资源被中间件拦截。忘记排除 .js、.css、图片等静态文件路径,会导致资源加载时也触发语言检测,严重影响性能。用 pathname.includes('.') 做简单判断,或者用 matcher 配置精确匹配。
2. SEO 的 hreflang 标签。搜索引擎需要知道同一页面的不同语言版本。在 <head> 中加入:
<link rel="alternate" hreflang="zh" href="https://example.com/blog" />
<link rel="alternate" hreflang="en" href="https://example.com/en/blog" />
3. Sitemap 中包含所有语言版本。每个页面在 sitemap 中应该有中英文两个条目,帮助搜索引擎发现和索引所有版本。
总结
Next.js Middleware 的「请求前拦截」特性非常适合做多语言路由。核心设计是:Cookie 优先 → Accept-Language 兜底 → 默认语言无前缀。这套方案对 SEO 友好,用户体验也自然——首次访问自动匹配浏览器语言,手动切换后永久记住偏好。