hreflang Next.js: SEO bilingüe con App Router y sitemap
El SEO bilingüe falla por inconsistencias: hreflang sin retorno, canonical contradictorio y traducciones inexistentes. Cómo evitarlo con Next.js App Router.
PorSergio Jáuregui
8 min de lectura
En este artículo
- Por qué el SEO bilingüe falla por inconsistencias
- Hreflang Next.js: una única fuente de verdad
- Una URL por idioma: subcarpeta, subdominio o dominio
- Cuándo no montaría dos idiomas
- Sitemap con alternates y lastmod real
- La traducción que aún no existe: hreflang ausente a propósito
- JSON-LD con inLanguage y una revisión antes de publicar
- Verifica tu hreflang en Search Console esta semana
Cuando un sitio con hreflang Next.js falla, casi nunca es por sintaxis: falla por inconsistencias. Un alterno sin retorno, una canónica que contradice al grupo o un hreflang que apunta a una traducción que aún no existe bastan para que Google ignore la parte incoherente. La solución es una única fuente de verdad: las traducciones publicadas mandan, y canónicas, alternos y sitemap derivan de ellas.
Esta guía recorre el SEO bilingüe con App Router: reciprocidad, canónicas, sitemap, traducciones pendientes, datos estructurados y verificación. El código que aparece es una implementación de referencia de técnicas documentadas, no una receta única para cualquier proyecto.
Por qué el SEO bilingüe falla por inconsistencias
Hreflang funciona como un grupo: cada versión de una página enlaza a todas las demás, incluida ella misma. La documentación de Google sobre versiones localizadas pide que cada enlace tenga su retorno: si la página X apunta a la Y, la Y debe apuntar de vuelta a la X. Cuando falta un retorno, Google puede ignorar el enlace y la versión en otro idioma queda fuera del grupo.
Los tres fallos típicos son de coherencia. Primero, alternos no recíprocos: el español declara el inglés, pero el inglés no devuelve el enlace. Es algo habitual en plantillas editadas a mano por idioma. Segundo, canónica contradictoria: una traducción que se canoniza hacia otro idioma le dice a Google que es un duplicado, justo lo contrario de lo que afirma el hreflang. Tercero, hreflang hacia lo inexistente: alternos que apuntan a un borrador o a una URL con 404, porque se escribieron una vez y nadie los actualizó al despublicar.
La cura es arquitectónica: ningún alterno escrito a mano. Si los alternos se derivan de las traducciones publicadas, es imposible declarar un idioma que no existe. Esa regla elimina de raíz dos de los tres fallos; la canónica merece su propio apartado.
Hreflang Next.js: una única fuente de verdad
En el App Router, los alternos se declaran en los metadatos. La documentación de Next.js sobre alternates define canonical y languages, y el framework emite los link en el head. El patrón útil es una función pura que recibe las rutas publicadas de cada idioma y devuelve solo lo que es verdad:
// Implementación de referencia: el alterno solo existe si la traducción está publicada
type Slugs = { es: string | null; en: string | null };
export function alternatesFor(base: string, locale: 'es' | 'en', slugs: Slugs) {
const urlFor = (lang: 'es' | 'en', slug: string) =>
lang === 'es' ? `${base}/${slug}` : `${base}/en/${slug}`;
const current = slugs[locale];
if (!current) return {};
const alternates: { canonical: string; languages?: Record<string, string> } = {
canonical: urlFor(locale, current),
};
if (slugs.es && slugs.en) {
alternates.languages = {
es: urlFor('es', slugs.es),
en: urlFor('en', slugs.en),
'x-default': urlFor('es', slugs.es),
};
}
return alternates;
}
Dos reglas sostienen la coherencia. La canónica es la URL del idioma actual, sin barra final. Y languages solo se emite cuando ambas traducciones están publicadas: con una sola, un hreflang solitario no aporta nada, y uno hacia lo inexistente daña. Ojo con los slugs: si el español y el inglés tienen slugs distintos, la función debe recibir ambos, como aquí.
Como referencia, el blog de sergioja.com aplica este mismo convenio, con español en la raíz e inglés bajo /en. El código de arriba es una versión simplificada del principio; el proyecto de referencia muestra cómo encaja en un sitio completo.
Una URL por idioma: subcarpeta, subdominio o dominio
Hay tres formas de separar idiomas: subcarpeta (/en/...), subdominio (en.tudominio.com) o un dominio por idioma. La subcarpeta mantiene todo bajo un mismo dominio, concentra las señales y simplifica el mantenimiento: un despliegue, un sitemap, una sola propiedad en Search Console. El subdominio o el dominio separado dividen el sitio y solo compensan con operaciones realmente separadas por mercado.
Para un proyecto bilingüe pequeño, la subcarpeta suele ser la opción más sensata. Lo importante es que el convenio viva en un único lugar, un ayudante que localiza rutas y construye cada URL en cada idioma. Así, canónica, alternos y sitemap usan la misma regla, y la regla no depende de la memoria de quien escribe la siguiente página.
Cuándo no montaría dos idiomas
Dos idiomas mal mantenidos pierden contra uno bien cuidado. Si no puedes publicar y actualizar ambos con el mismo acabado, la traducción vieja confunde a quien lee y el hreflang roto confunde a Google. Un solo idioma sin hreflang es más honesto que dos con un grupo incoherente. La contrapartida real es el coste de mantenimiento: cada artículo, cada página y cada cambio de estructura se duplica.
Sitemap con alternates y lastmod real
El sitemap repite lo que dice el head en formato rastreable, y debe decir exactamente lo mismo. Si discrepan, Google descarta la parte incoherente. Un sitemap multilingüe bien construido tiene tres bloques: la portada, que existe en ambos idiomas y se declara traducción de sí misma; las categorías, emparejadas por identificador entre idiomas; y los contenidos, con su fecha real de actualización y solo con alternos cuando ambos idiomas existen.
Hay dos detalles fáciles de pasar por alto. El primero: no duplicar URL. Si un contenido llega repetido, el constructor debe conservar una sola entrada. El segundo: la forma de la URL. Si la canónica no lleva barra final, el sitemap tampoco debe llevarla; mezclar variantes divide las señales. El robots.txt debe permitir el rastreo, bloquear las rutas de API y de vista previa, y declarar el sitemap.
Las listas de contenido suelen cachearse para no consultar la base de datos en cada visita. Esa caché debe invalidarse al publicar, no solo por tiempo. El mecanismo y sus trampas están en el artículo sobre revalidateTag en Next.js.
La traducción que aún no existe: hreflang ausente a propósito
Cada traducción tiene su propio estado: borrador, programada, publicada o archivada. Eso permite publicar el español hoy y el inglés la semana que viene, y plantea una pregunta incómoda: ¿qué hreflang emite el español mientras tanto? La respuesta correcta es ninguno hacia el idioma que falta. Los metadatos solo reciben rutas publicadas, el sitemap solo incluye alternos cuando ambos idiomas existen, y el selector de idioma aparece atenuado en lugar de enlazar a un 404. La ausencia deliberada gana a la promesa rota.
Lo mismo aplica a los casos límite. Si un slug no existe, lo razonable es una redirección permanente que conserve el idioma o, si no hay destino, un 404 real. Un fallo temporal de la API nunca debe convertirse en un 404 cacheado, porque entonces un artículo real pierde posición por un problema pasajero: solo la ausencia confirmada debe devolver nulo. Y la vista previa de un borrador debe ir sin índice ni seguimiento, y declarar canónica pública solo cuando el contenido ya está publicado.
Un límite habitual: el caso de la traducción pendiente rara vez se prueba si siempre se publican ambos idiomas a la vez. La experiencia real llega cuando un idioma se retrasa, así que conviene simularlo antes de necesitarlo.
JSON-LD con inLanguage y una revisión antes de publicar
Cada contenido debería emitir datos estructurados coherentes con su idioma. Un BlogPosting con titular, descripción, URL, idioma, fechas y autor; un BreadcrumbList que traza portada, categoría y contenido; y un FAQPage solo cuando hay preguntas reales en la página. El idioma viaja en inLanguage, así que cada traducción se describe sin ambigüedad.
Antes de publicar, conviene revisar la pareja hreflang y canónica en este orden. Primero, cada traducción se canoniza a sí misma; cruzar la canónica entre idiomas declara un duplicado y contradice al grupo. Segundo, cada alterno existe publicado y devuelve su enlace. Tercero, x-default apunta a una página real. Cuarto, canónica, alternos y sitemap usan la misma forma de URL. Quinto, el head y el sitemap dicen lo mismo.
Un error que conviene evitar por diseño es mezclar idiomas en los enlaces del cuerpo. Cada enlace interno debe usar las rutas de su propio idioma, y el cambio de idioma vive en la cabecera, no dentro del texto. Así el lector nunca aterriza en el idioma equivocado al seguir un enlace.
Verifica tu hreflang en Search Console esta semana
Si tu hreflang Next.js pasa tres comprobaciones, funciona. Primera, abre el código fuente de cada traducción y confirma que los alternos existen, se apuntan entre sí e incluyen la propia página. Segunda, inspecciona ambas URL en Search Console y confirma que Google ve el HTML renderizado con esos alternos, no una carcasa vacía. Tercera, envía el sitemap y observa en el informe de rendimiento qué URL gana impresiones por país: si cada versión gana donde debe, el grupo funciona.
Si además tu portada usa un 3D, revisa primero que el texto llegue en el HTML del servidor, como explica el artículo sobre sitio web 3D SEO. Y si la web entera no aparece en Google, el problema suele ser de superficie y no de idiomas: empieza por web nueva no aparece en Google.
Si tu sitio ya tiene contenido en dos idiomas, el orden es este: unifica el convenio de URL, deriva alternos y canónicas de las traducciones publicadas, iguala sitemap y head, y mide. Si quieres que revisemos tu caso bilingüe, escríbeme desde la página de contacto.
¿Tienes un proyecto en mente?
Soy Sergio Jáuregui, desarrollador Full Stack. Si te ha gustado este artículo, mira mi trabajo o escríbeme y lo hablamos.
Seguir leyendo
SEO y visibilidadGuía
Web nueva no aparece en Google: qué revisar en Search Console
Si tu web nueva no aparece en Google, empieza por impresiones, páginas, consultas y sitemaps. Qué mirar, cómo leer cifras pequeñas y un plan de acción.
Actualizado el 15 oct 2026
Volver arriba