Manejo de códigos de idioma no soportados

Problema

Una aplicación utiliza la ruta URL (por ejemplo, /en/, /fr/) para determinar el idioma, pero los usuarios pueden escribir manualmente cualquier valor, como /xx/about. Cuando este valor no coincide con un idioma compatible, la aplicación puede bloquearse, mostrar un error genérico o mostrar contenido sin traducir, sin guiar al usuario de vuelta a una experiencia válida.

Solución

Utiliza un middleware para interceptar todas las solicitudes entrantes. Este middleware validará el código de idioma de la URL contra una lista definitiva de idiomas compatibles. Si el código no es compatible, la solicitud se reescribirá a una página "No encontrada" antes de que llegue a la lógica de la aplicación.

Pasos

1. Define tus idiomas compatibles

Crea un archivo de configuración central para almacenar tu lista de códigos de idioma válidos (locales). Esto hace que la lista sea reutilizable para tu middleware y otras partes de tu aplicación.

// i18n.config.ts
export const locales = ['en', 'es', 'fr'];

2. Crea el archivo middleware

Crea un nuevo archivo llamado middleware.ts en la raíz de tu proyecto (o en tu directorio src/). Next.js detectará automáticamente este archivo y lo ejecutará en las solicitudes.

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { locales } from './i18n.config';

export function middleware(request: NextRequest) {
  // La lógica irá aquí en el siguiente paso
}

3. Añade la lógica de validación

Dentro de la función middleware, obtén el pathname de la solicitud. Necesitamos verificar el primer segmento de la ruta (por ejemplo, en en /en/about) y ver si es un idioma válido y compatible.

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { locales } from './i18n.config';

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // 1. Maneja la ruta raíz por separado
  // (Esto será manejado por la siguiente receta: "Detectando el
  // idioma preferido de un usuario"). Por ahora, simplemente lo dejamos pasar.
  if (pathname === '/') {
    return NextResponse.next();
  }

  // 2. Extrae el código de idioma de la ruta
  const langCode = pathname.split('/')[1];

  // 3. Comprueba si el código de idioma está en nuestra lista
  if (locales.includes(langCode)) {
    // El idioma es válido, continúa a la página solicitada
    return NextResponse.next();
  }

  // 4. Si el idioma no es válido, reescribe a una página 404
  // Esto mantiene la URL inválida en la barra del navegador
  const url = request.nextUrl.clone();
  url.pathname = `/404`; // Asume que tienes un archivo app/404.tsx
  return NextResponse.rewrite(url);
}

Esta lógica verifica cada solicitud. Si la URL es /fr/about, langCode es fr, se encuentra en locales, y la solicitud continúa. Si la URL es /xx/about, langCode es xx, no se encuentra, y al usuario se le muestra la página 404 sin que la aplicación intente procesar la solicitud inválida.

4. Configurar el matcher del middleware

Para hacer que tu middleware sea más eficiente, debes indicarle qué rutas debe procesar. Queremos que se ejecute en las solicitudes de página pero que omita los archivos estáticos y las rutas de API.

Añade un objeto config al final de tu archivo middleware.ts.

// middleware.ts

// ... (la función middleware de arriba)

export const config = {
  matcher: [
    // Omitir todas las rutas que comienzan con:
    // - api (rutas de API)
    // - _next/static (archivos estáticos)
    // - _next/image (archivos de optimización de imágenes)
    // - favicon.ico (archivo favicon)
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

Esta expresión regular indica al middleware que se ejecute en todas las rutas excepto en aquellas que son típicamente para activos estáticos o llamadas a API. Esto evita validaciones innecesarias en cada solicitud de imagen, fuente o datos.