Как валидировать параметры локали в URL в React Router v7
Корректная обработка неподдерживаемых кодов локалей
Проблема
Когда идентификаторы локалей становятся частью структуры URL, они превращаются во ввод пользователя, который может содержать любые значения. Пользователь может вручную ввести /xx/about, /gibberish/contact или любой другой некорректный код локали в адресную строку. Без валидации приложению нужно решать, как обрабатывать такие неверные значения. Если пропускать невалидные локали дальше, это может привести к отсутствию переводов, некорректному форматированию или ошибкам во время выполнения, когда i18n-библиотеки попытаются загрузить несуществующие данные локали. Если молча возвращать пользователя к локали по умолчанию, не уведомляя его, это вызывает путаницу — человек не понимает, на каком языке он сейчас находится. А если показывать ошибку или пустую страницу, пользователь просто теряется и не знает, что делать дальше.
Сложность задачи в том, что валидация локали должна происходить на самом раннем этапе обработки запроса — до рендера компонентов и до загрузки переводов. Если проверка выполняется слишком поздно, приложение уже может попытаться получить ресурсы для несуществующей локали или инициализировать i18n-провайдеры с некорректной конфигурацией, что приводит к лишним затратам и возможным сбоям.
Решение
Проверяйте параметр локали в загрузчике маршрута до рендера страницы. Загрузчики в React Router выполняются до монтирования компонентов, поэтому это идеальное место для проверки, поддерживается ли запрошенная локаль в вашем приложении. Если локаль валидна — продолжайте обработку запроса как обычно. Если локаль не поддерживается, сразу перенаправьте пользователя на безопасный вариант: либо на тот же путь с локалью по умолчанию, либо на отдельную страницу с объяснением проблемы.
Такой подход не позволяет недопустимым локалям попадать в ваши компоненты и провайдеры i18n. Возвращая ответ с редиректом из загрузчика, вы используете встроенную навигационную систему React Router для аккуратной обработки ошибки. Редирект происходит на сервере при SSR или на клиенте при навигации, что обеспечивает одинаковое поведение при разных стратегиях рендеринга. Пользователь сразу видит изменение URL, а ваше приложение не пытается загрузить ресурсы для несуществующих локалей.
Шаги
1. Определите поддерживаемые локали
Создайте список допустимых кодов локалей, которые поддерживает ваше приложение. Этот список будет основой для валидации.
export const SUPPORTED_LOCALES = ["en", "es", "fr", "de", "ja"] as const;
export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
export function isValidLocale(locale: string): locale is SupportedLocale {
return SUPPORTED_LOCALES.includes(locale as SupportedLocale);
}
Эта вспомогательная функция обеспечивает типобезопасную валидацию и может использоваться повторно в загрузчиках и других частях приложения.
2. Добавьте валидацию в загрузчик маршрута с префиксом локали
В модуле маршрута для страниц с префиксом локали экспортируйте загрузчик, который проверяет параметр локали и делает редирект, если он недопустим.
import type { Route } from "./+types/page";
import { redirect } from "react-router";
import { isValidLocale } from "~/i18n/locales";
export async function loader({ params }: Route.LoaderArgs) {
const { locale } = params;
if (!locale || !isValidLocale(locale)) {
return redirect("/en/not-found");
}
return { locale };
}
export default function Page({ loaderData }: Route.ComponentProps) {
return (
<div>
<h1>Content in {loaderData.locale}</h1>
</div>
);
}
Загрузчик извлекает локаль из URL, проверяет её и перенаправляет на безопасный маршрут, если валидация не проходит. Если локаль допустима, возвращаются данные для компонентов.
3. Настройте маршруты с параметрами локали
В вашем файле routes.ts определите маршруты, где локаль будет динамическим сегментом.
import { type RouteConfig, route } from "@react-router/dev/routes";
export default [
route(":locale/about", "./routes/about.tsx"),
route(":locale/contact", "./routes/contact.tsx"),
route(":locale/not-found", "./routes/not-found.tsx"),
] satisfies RouteConfig;
Каждый маршрут с параметром :locale вызывает свой загрузчик, где валидация происходит до рендера компонента.
4. Создайте страницу «не найдено» для недопустимых локалей
Сделайте отдельную страницу, которая объясняет, что локаль не найдена, и предлагает варианты перехода.
import { Link } from "react-router";
import { SUPPORTED_LOCALES } from "~/i18n/locales";
export default function NotFound() {
return (
<div>
<h1>Language Not Found</h1>
<p>The requested language is not supported.</p>
<nav>
<p>Choose a language:</p>
<ul>
{SUPPORTED_LOCALES.map((locale) => (
<li key={locale}>
<Link to={`/${locale}`}>{locale.toUpperCase()}</Link>
</li>
))}
</ul>
</nav>
</div>
);
}
На этой странице даётся понятная обратная связь и конкретные шаги для решения проблемы, чтобы пользователь мог восстановиться после ошибки, не покидая приложение.
5. Добавьте маршрут-перехватчик для полностью некорректных путей
Для URL-адресов, которые не соответствуют ни одному из заданных шаблонов маршрутов, добавьте маршрут с «splat» в конце вашей конфигурации маршрутов.
import { type RouteConfig, route } from "@react-router/dev/routes";
export default [
route(":locale/about", "./routes/about.tsx"),
route(":locale/contact", "./routes/contact.tsx"),
route(":locale/not-found", "./routes/not-found.tsx"),
route("*", "./routes/catch-all.tsx"),
] satisfies RouteConfig;
Маршрут с «splat» совпадает с любым путём, который не подходит под предыдущие маршруты, позволяя отдельно обрабатывать полностью некорректные URL, отличая их от неправильных кодов локали.
6. При желании перенаправляйте на локаль по умолчанию вместо страницы с ошибкой
Если вы хотите тихо исправлять некорректные локали, а не показывать ошибку, перенаправляйте на тот же путь с локалью по умолчанию.
import type { Route } from "./+types/page";
import { redirect } from "react-router";
import { isValidLocale } from "~/i18n/locales";
const DEFAULT_LOCALE = "en";
export async function loader({ params, request }: Route.LoaderArgs) {
const { locale } = params;
if (!locale || !isValidLocale(locale)) {
const url = new URL(request.url);
const newPath = url.pathname.replace(/^\/[^/]+/, `/${DEFAULT_LOCALE}`);
return redirect(newPath);
}
return { locale };
}
Такой подход сохраняет остальную часть URL, заменяя только некорректный сегмент локали, что обеспечивает более плавный опыт для пользователя, если проблема только в локали.