Cómo validar parámetros de locale en URLs en React Router v7

Gestiona códigos de locale no soportados de forma elegante

Problema

Cuando los identificadores de locale se convierten en parte de la estructura de URL, se transforman en entrada de usuario que puede contener cualquier valor arbitrario. Un usuario podría escribir manualmente /xx/about, /gibberish/contact o cualquier otro código de locale inválido en la barra de direcciones. Sin validación, la aplicación debe decidir cómo gestionar estas entradas inválidas. Permitir que locales inválidos continúen puede provocar traducciones faltantes, formato incorrecto o errores en tiempo de ejecución cuando las bibliotecas de i18n intentan cargar datos de locale inexistentes. Recurrir silenciosamente a un locale predeterminado 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 sin una ruta clara para continuar.

El desafío se complica por el hecho de que la validación de locale 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 del locale o inicializado proveedores de i18n con configuración inválida, desperdiciando recursos y potencialmente causando fallos en cascada.

Solución

Valida el parámetro de locale en un loader de ruta antes de que la página se renderice. Los loaders en React Router se ejecutan antes de que los componentes se monten, convirtiéndolos en el lugar ideal para verificar si el locale solicitado existe en la lista de idiomas soportados de tu aplicación. Si el locale es válido, permite que la solicitud continúe normalmente. Si el locale es inválido, redirige inmediatamente al usuario a una alternativa segura: ya sea la misma ruta con un locale predeterminado válido, o una página dedicada de no encontrado que explique el problema.

Este enfoque evita que locales no válidos lleguen a tus componentes y proveedores de i18n. Al devolver una respuesta de redirección desde el loader, aprovechas el sistema de navegación integrado de React Router para manejar el error de forma elegante. La redirección ocurre en el servidor durante SSR o en el cliente durante la navegación, garantizando un comportamiento consistente en todas las 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 locales inexistentes.

Pasos

1. Define tus locales compatibles

Crea una lista de códigos de locale válidos que tu aplicación admite. 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 redirige a una alternativa segura si la validación falla. Si el locale es válido, devuelve datos que los componentes pueden usar.

3. Configura tus rutas con parámetros de locale

En tu archivo routes.ts, define rutas que incluyan el locale como 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. Crea una página de no encontrado para locales no vá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>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>
  );
}

Esta página proporciona retroalimentación clara y próximos pasos accionables, ayudando a los usuarios a recuperarse del error sin abandonar tu aplicación.

5. Añade una ruta catch-all para rutas completamente inválidas

Para URLs que no coincidan con ningún patrón de ruta definido, añade una ruta splat 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 splat coincide con cualquier ruta que no coincida con rutas anteriores, permitiéndote manejar URLs completamente mal formadas por separado de códigos de locale inválidos.

6. Opcionalmente redirige a un locale predeterminado en lugar de una página de no encontrado

Si prefieres corregir silenciosamente 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.