如何在 Next.js(Pages Router)v16 中验证 URL 中的 locale 参数

优雅处理不支持的 locale 代码

问题

当 locale 代码成为 URL 结构的一部分时,它们就变成了需要验证的用户输入。访问者可以像输入 /xx/about/gibberish/contact 一样轻松地输入 /en/about 这样的有效 locale。如果不进行验证,应用可能会崩溃、显示异常内容,或出现令人困惑的错误信息。遇到无效 locale URL 的用户需要明确的后续指引——要么重定向到有效 locale,要么返回合适的“未找到”页面,帮助他们理解问题所在。

解决方案

在请求到达页面组件之前,先将传入的 locale 参数与支持的 locale 列表进行校验。使用 Next.js 的 middleware 拦截请求,检查 URL 中的 locale 是否为受支持的值,并做出相应处理。对于无效的 locale,可以将用户重定向到默认 locale,或重写请求以显示 404 页面。这样可以确保只有有效的 locale 代码才能渲染内容,而无效代码则被优雅处理,不会破坏用户体验。

步骤

1. 在 Next.js 配置中定义支持的 locale

next.config.js 中配置 i18n 设置,声明应用支持哪些 locale 以及默认 locale。

module.exports = {
  i18n: {
    locales: ["en", "fr", "de"],
    defaultLocale: "en",
    localeDetection: false,
  },
};

localeDetection 设置为 false,可以防止根据浏览器偏好自动重定向,从而让你完全掌控 locale 处理逻辑。

2. 创建 middleware 验证 locale 参数

在项目根目录下创建 middleware.ts 文件,或者如果你有 src 文件夹,也可以放在里面。

import { NextRequest, NextResponse } from "next/server";

const SUPPORTED_LOCALES = ["en", "fr", "de"];
const DEFAULT_LOCALE = "en";

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  const pathnameHasLocale = SUPPORTED_LOCALES.some(
    (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`,
  );

  if (!pathnameHasLocale) {
    return;
  }

  const localeInPath = pathname.split("/")[1];

  if (!SUPPORTED_LOCALES.includes(localeInPath)) {
    const url = request.nextUrl.clone();
    url.pathname = pathname.replace(`/${localeInPath}`, `/${DEFAULT_LOCALE}`);
    return NextResponse.redirect(url);
  }
}

export const config = {
  matcher: ["/((?!_next|api|favicon.ico|.*\\..*).*)"],
};

中间件会从 URL 路径中提取 locale,与支持的 locale 数组进行校验,并在 locale 无效时将其重定向到默认 locale,同时保留其余路径。

3. 使用 404 响应处理无效的 locale

如果你希望显示 404 页面而不是重定向,可以将请求重写到自定义 404 页面。

import { NextRequest, NextResponse } from "next/server";

const SUPPORTED_LOCALES = ["en", "fr", "de"];

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  const pathnameHasLocale = SUPPORTED_LOCALES.some(
    (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`,
  );

  if (!pathnameHasLocale) {
    return;
  }

  const localeInPath = pathname.split("/")[1];

  if (!SUPPORTED_LOCALES.includes(localeInPath)) {
    const url = request.nextUrl.clone();
    url.pathname = "/404";
    return NextResponse.rewrite(url);
  }
}

export const config = {
  matcher: ["/((?!_next|api|favicon.ico|.*\\..*).*)"],
};

pages/404.js 处创建一个自定义 404 页面,该页面会在构建时静态生成。

4. 创建自定义 404 页面

export default function Custom404() {
  return (
    <div>
      <h1>404 - Page Not Found</h1>
      <p>The page you are looking for does not exist.</p>
    </div>
  );
}

当中间件重写无效 locale 请求时,将显示此页面,为用户提供明确的提示,说明所请求的 URL 不可用。