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
routingcentral. - Una locale desconocida provoca un
notFound()limpio en lugar de una página rota. - La navegación pasa por un
Linklocalizado 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
generateAlternatesque produce las etiquetasalternate. - 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. setRequestLocaleantes de las traducciones: innegociable para mantener lo estático.- Los
hreflangse 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.