React Router v7에서 URL의 로케일 매개변수를 검증하는 방법
지원되지 않는 로케일 코드를 우아하게 처리하기
문제
로케일 식별자가 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>언어를 찾을 수 없음</h1>
<p>요청한 언어는 지원되지 않습니다.</p>
<nav>
<p>언어 선택:</p>
<ul>
{SUPPORTED_LOCALES.map((locale) => (
<li key={locale}>
<Link to={`/${locale}`}>{locale.toUpperCase()}</Link>
</li>
))}
</ul>
</nav>
</div>
);
}
이 페이지는 명확한 피드백과 실행 가능한 다음 단계를 제공하여 사용자가 애플리케이션을 떠나지 않고도 오류에서 복구할 수 있도록 도와줍니다.
5. 완전히 유효하지 않은 경로를 위한 캐치올 라우트 추가하기
정의된 라우트 패턴과 일치하지 않는 URL의 경우, 라우트 구성 끝에 스플랫 라우트를 추가하세요.
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;
스플랫 라우트는 이전 라우트와 일치하지 않는 모든 경로와 일치하므로, 잘못된 로케일 코드와 별도로 완전히 잘못된 형식의 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 경로의 나머지 부분을 보존하여, 로케일만이 문제일 때 더 부드러운 사용자 경험을 제공합니다.