Saltar al contenido principal

Un sitio Next.js verdaderamente multilingüe con next-intl

Routing localizado, renderizado estático, hreflang y cambio de idioma: cómo estructuro un sitio Next.js en diez idiomas sin sacrificar el rendimiento ni el SEO.

Por qué el multilingüe es un verdadero tema de arquitectura

Traducir una interfaz es fácil. Hacer un sitio multilingüe correcto —indexable, rápido, mantenible— es una decisión de arquitectura que se toma al principio, no al final. Este portfolio existe en diez idiomas, y estas son las decisiones que aguantan a escala.

El routing por segmento de locale

El enfoque más robusto con el App Router: un segmento dinámico [locale] en la raíz. Cada página vive bajo app/[locale]/…, y el idioma se convierte en un dato de URL de primer orden (/fr/projects, /en/projects).

Con next-intl, ese segmento se valida en el renderizado:

  • Las locales admitidas se listan en una configuración routing central.
  • Una locale desconocida provoca un notFound() limpio en lugar de una página rota.
  • La navegación pasa por un Link localizado que prefija automáticamente el idioma correcto.

La ventaja: los componentes nunca manipulan la locale «a mano». Piden una traducción y el sistema se encarga del resto.

La trampa del renderizado estático

Es el error que más cuesta en producción. Si llamas a las traducciones sin fijar la locale primero, next-intl lee las cabeceras de la petición. Resultado: la página pasa a renderizado dinámico y pierdes el prerenderizado estático — a veces con un error DYNAMIC_SERVER_USAGE en producción.

La solución cabe en una línea, justo al principio de cada página:

setRequestLocale(locale);

Llamada antes de cualquier getTranslations, garantiza un renderizado totalmente estático. Combinada con un generateStaticParams que cruza locale × slug, prerenderiza cada página en cada idioma en el build.

El multilingüe estático no es más lento que el monolingüe. Solo es más exigente con el orden de las llamadas.

Los hreflang, o cómo evitar competir contigo mismo

Sin ninguna indicación, Google trata tus diez versiones lingüísticas como diez páginas rivales por las mismas palabras clave. La solución es el hreflang: cada página declara el conjunto de sus variantes.

Dos lugares deben permanecer sincronizados:

  • Los metadatos de cada página — mediante un helper generateAlternates que produce las etiquetas alternate.
  • El sitemap — cada URL lista allí sus idiomas y un x-default.

La coherencia entre estas dos fuentes es lo que hace que una búsqueda en español caiga en /es y no en /fr.

El cambio de idioma, del lado del usuario

Un selector de idioma debe cambiar la locale sin perder la página actual. El reflejo ingenuo —devolver a la portada— es frustrante. Con el usePathname localizado de next-intl, recuperas la ruta sin prefijo y la reemites en el idioma elegido. El usuario permanece exactamente donde estaba.

Lo que me quedo

  • El segmento [locale] en la raíz es la base; todo se deriva de él.
  • setRequestLocale antes de las traducciones: innegociable para mantener lo estático.
  • Los hreflang se gestionan por partida doble (metadatos y sitemap), siempre sincronizados.
  • La dirección del texto (RTL para el árabe) se controla a nivel de <html dir>, no página por página.

Bien hecho, el multilingüe se vuelve invisible: cada visitante siente que el sitio se escribió para él, y Google sabe exactamente qué puerta abrirle.

AGENTS.md y los agentes de código: el nuevo herramental del desarrollador