Cómo validar parámetros de localización en URLs en React Router v7
Gestionar códigos de localización no soportados de manera elegante
Problema
Cuando los identificadores de localización forman parte de la estructura de URL, se convierten en entradas de usuario que pueden contener cualquier valor arbitrario. Un usuario podría escribir manualmente /xx/about, /gibberish/contact, o cualquier otro código de localización inválido en la barra de direcciones. Sin validación, la aplicación debe decidir cómo manejar estas entradas inválidas. Permitir que las localizaciones inválidas continúen puede llevar a traducciones faltantes, formato incorrecto o errores en tiempo de ejecución cuando las bibliotecas de i18n intentan cargar datos de localización inexistentes. Recurrir silenciosamente a una localización predeterminada sin informar al usuario crea confusión sobre qué idioma están visualizando. Mostrar límites de error o páginas en blanco deja a los usuarios varados sin un camino claro a seguir.
El desafío se complica por el hecho de que la validación de la localización debe ocurrir temprano en el ciclo de vida de la solicitud, antes de que los componentes se rendericen y antes de que se carguen los datos de traducción. Si la validación ocurre demasiado tarde, la aplicación puede haber intentado ya obtener recursos específicos de la localización o inicializado proveedores de i18n con configuración inválida, desperdiciando recursos y potencialmente causando fallos en cascada.
Solución
Validar el parámetro de localización en un cargador de ruta antes de que la página se renderice. Los cargadores en React Router se ejecutan antes de que los componentes se monten, lo que los convierte en el lugar ideal para verificar si la localización solicitada existe en la lista de idiomas soportados por tu aplicación. Si la localización es válida, permite que la solicitud continúe normalmente. Si la localización es inválida, redirige inmediatamente al usuario a una alternativa segura—ya sea la misma ruta con una localización predeterminada válida, o una página dedicada de no encontrado que explique el problema.
Este enfoque evita que las localizaciones inválidas lleguen a tus componentes y proveedores de i18n. Al devolver una respuesta de redirección desde el cargador, aprovechas el sistema de navegación incorporado de React Router para manejar el error con elegancia. La redirección ocurre en el servidor durante SSR o en el cliente durante la navegación, asegurando un comportamiento consistente a través de estrategias de renderizado. Los usuarios reciben retroalimentación inmediata a través del cambio de URL, y tu aplicación evita intentar cargar recursos para localizaciones inexistentes.
Pasos
1. Define tus locales soportados
Crea una lista de códigos de locale válidos que tu aplicación soporte. Esta lista sirve como fuente de verdad para la validación.
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);
}
Esta función auxiliar proporciona validación con seguridad de tipos y puede reutilizarse en loaders y otras partes de tu aplicación.
2. Añade validación al loader de tu ruta con prefijo de locale
En el módulo de ruta para tus páginas con prefijo de locale, exporta un loader que verifique el parámetro de locale y redirija si no es válido.
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>
);
}
El loader extrae el locale de la URL, lo valida y redirecciona a una alternativa segura si la validación falla. Si el locale es válido, devuelve datos que los componentes pueden utilizar.
3. Configura tus rutas con parámetros de locale
En tu archivo routes.ts, define rutas que incluyan el locale como un segmento dinámico.
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;
Cada ruta con un parámetro :locale invocará su loader, donde ocurre la validación antes de que el componente se renderice.
4. Crear una página not-found para locales inválidos
Construye una página dedicada que explique que el locale no fue encontrado y ofrezca opciones de navegación.
import { Link } from "react-router";
import { SUPPORTED_LOCALES } from "~/i18n/locales";
export default function NotFound() {
return (
<div>
<h1>Idioma no encontrado</h1>
<p>El idioma solicitado no está soportado.</p>
<nav>
<p>Elige un idioma:</p>
<ul>
{SUPPORTED_LOCALES.map((locale) => (
<li key={locale}>
<Link to={`/${locale}`}>{locale.toUpperCase()}</Link>
</li>
))}
</ul>
</nav>
</div>
);
}
Esta página proporciona retroalimentación clara y pasos accionables a seguir, ayudando a los usuarios a recuperarse del error sin abandonar tu aplicación.
5. Añadir una ruta catch-all para rutas completamente inválidas
Para URLs que no coinciden con ningún patrón de ruta definido, añade una ruta comodín al final de tu configuración de rutas.
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;
La ruta comodín coincide con cualquier ruta que no coincida con las rutas anteriores, permitiéndote manejar URLs completamente malformadas de manera separada de los códigos de locale inválidos.
6. Opcionalmente redirigir a un locale predeterminado en lugar de una página not-found
Si prefieres corregir silenciosamente los locales inválidos en lugar de mostrar un error, redirige a la misma ruta con un locale predeterminado.
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 };
}
Este enfoque preserva el resto de la ruta URL mientras reemplaza solo el segmento de locale inválido, proporcionando una experiencia de usuario más fluida cuando el locale es el único problema.