Как валидировать параметры локали в URL в Next.js (Pages Router) v16

Корректная обработка неподдерживаемых кодов локалей

Проблема

Когда коды локалей становятся частью структуры URL, они превращаются во ввод пользователя, который нужно валидировать. Посетитель может ввести /xx/about или /gibberish/contact так же легко, как и корректную локаль вроде /en/about. Без валидации приложение может упасть, показать некорректный контент или выдать непонятные ошибки. Пользователям, которые попали на URL с невалидной локалью, нужен понятный выход — например, редирект на поддерживаемую локаль или корректная страница «Не найдено», объясняющая, что произошло.

Решение

Валидируйте входящие параметры локали по списку поддерживаемых локалей до того, как запрос попадёт в компоненты страницы. Используйте middleware Next.js, чтобы перехватывать запросы, проверять, соответствует ли локаль в URL поддерживаемому значению, и реагировать соответствующим образом. Для невалидных локалей перенаправляйте пользователя на локаль по умолчанию или переписывайте запрос на страницу 404. Это гарантирует, что только корректные коды локалей попадут к рендеру контента, а ошибочные будут обработаны аккуратно, не ломая пользовательский опыт.

Шаги

1. Определите поддерживаемые локали в конфиге Next.js

Настройте параметры i18n в next.config.js, чтобы указать, какие локали поддерживает ваше приложение и какая локаль будет использоваться по умолчанию.

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

Если установить localeDetection в false, это отключит автоматические редиректы на основе предпочтений браузера, и вы получите полный контроль над обработкой локалей.

2. Создайте middleware для валидации параметров локали

Создайте файл 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, проверяет её в массиве поддерживаемых локалей и перенаправляет некорректные локали на локаль по умолчанию, сохраняя остальную часть пути.

3. Обработка некорректных локалей с помощью ответа 404

Если вы хотите показывать страницу 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|.*\\..*).*)"],
};

Создайте пользовательскую страницу 404 по адресу pages/404.js, которая будет статически сгенерирована во время сборки.

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>
  );
}

Эта страница отображается, когда промежуточное ПО переписывает запросы с некорректной локалью, показывая пользователям понятное сообщение о том, что запрошенный URL недоступен.