返回博客列表
Next.js国际化中间件

Next.js 中间件实现多语言路由与内容协商

用 Next.js Middleware 结合 Cookie 与 Accept-Language 实现智能多语言路由

作者: ekent·发布于 2026年1月17日

当你的网站需要支持中英文切换时,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 在每个请求到达页面之前执行,是做语言路由的理想位置。检测逻辑的优先级是:

  1. Cookie(用户手动切换过语言)
  2. Accept-Language 请求头(浏览器偏好)
  3. 默认语言(兜底为中文)
// 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 友好,用户体验也自然——首次访问自动匹配浏览器语言,手动切换后永久记住偏好。