Una web bilingüe no se convierte en internacional por traducir el menú y duplicar las páginas.
Cuando publiqué la versión inglesa del portfolio, ya tenía dos rutas para una misma pieza de contenido. Por ejemplo, un artículo vive en /blog/.../ y su versión en inglés en /en/blog/.../. Las dos páginas comparten tema, pero no deberían competir entre ellas ni obligar a Google a adivinar cuál mostrar a cada persona.
Ahí entra hreflang.
No es una etiqueta que traduzca una página ni una garantía de que Google vaya a mostrar siempre un idioma concreto. Sirve para declarar que varias URLs son versiones localizadas de un mismo contenido. Google usa esa información como una señal para intentar mostrar la versión más adecuada según el idioma o la región de búsqueda.
En mi caso, hreflang forma parte de una decisión más amplia. Quería que el portfolio pudiera crecer en español e inglés sin tener que acordarme de añadir etiquetas a mano cada vez que publicara un proyecto o artículo.
Es una pieza concreta del trabajo de SEO técnico del portfolio, pero aquí quería entrar en la parte que suele romperse cuando crecen las rutas.
El mapa de idiomas antes que las etiquetas
Lo primero que necesitaba resolver no era el HTML. Era saber cuál era la ruta equivalente de cada página.
Las rutas estáticas no siempre conservan el mismo nombre. La página española /sobre-mi/ corresponde a /en/about/, mientras que los artículos sí comparten slug y cambian de prefijo.
En el proyecto centralicé esas equivalencias en un helper. A partir de la ruta actual devuelve el par de URLs que representa la misma página en español e inglés.
export function getLocalePaths(pathname) {
const locale = getLocale(pathname);
const otherPath = getAlternate(pathname, locale);
return locale === 'es'
? { es: pathname, en: otherPath }
: { es: otherPath, en: pathname };
}Esto parece una parte pequeña del proyecto, pero evita que el SEO dependa de una lista dispersa de condiciones. El selector de idioma, las etiquetas del <head> y el sitemap parten de la misma relación entre rutas.
Si esa relación no existe o apunta a una página que no corresponde, hreflang no lo arregla. Solo estaría documentando mal la arquitectura.
Lo que recibe cada página
Con ese mapa, el componente SEO genera tres enlaces en el <head> de cada página.
<link rel="alternate" hreflang="es" href="https://www.alvarobarrena.com/blog/.../" />
<link rel="alternate" hreflang="en" href="https://www.alvarobarrena.com/en/blog/.../" />
<link rel="alternate" hreflang="x-default" href="https://www.alvarobarrena.com/blog/.../" />La primera etiqueta apunta a la versión española y la segunda a la inglesa. Las dos aparecen en ambas versiones de la página. Ese detalle importa. Google pide que cada versión se incluya a sí misma y enlace también a sus alternates. Si la página en español enlaza a la inglesa, pero la inglesa no devuelve el enlace, la relación puede ignorarse.
En este portfolio x-default apunta a español porque es el idioma principal y no hay una página independiente para elegir idioma. Lo uso como versión de reserva cuando no hay una alternativa declarada que encaje. No convierte la página española en una versión universal ni sustituye las etiquetas es y en.
Canonical y hreflang no hacen el mismo trabajo
Durante la implementación tuve que separar dos preguntas que es fácil mezclar.
La canonical responde cuál es la URL preferida de una página concreta. Por eso cada versión del portfolio lleva su canonical propia.
<link rel="canonical" href="https://www.alvarobarrena.com/en/blog/.../" />hreflang responde qué otras versiones localizadas pertenecen al mismo contenido. Una página inglesa no debería declarar como canonical la página española solo porque ambas hablen de lo mismo. Son URLs distintas para públicos distintos.
Si mezclas ambas cosas, puedes mandar señales contradictorias. La relación útil es que cada página sea canónica de sí misma y, además, declare sus alternativas reales.
También lo incluyo en el sitemap
El HTML es suficiente para declarar alternates. Google admite HTML, cabeceras HTTP o sitemap y no da una preferencia entre esos métodos.
En mi caso elegí mantenerlo también en el sitemap porque ya se genera desde los proyectos y artículos del sitio. Cada URL lleva sus variantes es, en y x-default.
<url>
<loc>https://www.alvarobarrena.com/blog/.../</loc>
<xhtml:link rel="alternate" hreflang="es" href="https://www.alvarobarrena.com/blog/.../" />
<xhtml:link rel="alternate" hreflang="en" href="https://www.alvarobarrena.com/en/blog/.../" />
</url>No añadiría las tres implementaciones solo por acumular señales. En este proyecto, el <head> y el sitemap se alimentan del mismo helper, así que mantener ambos no añade trabajo manual. Si tu mapa de idiomas vive en varios sitios y tienes que actualizarlo a mano, es fácil que una de las versiones se quede atrás.
Los errores que revisaría antes de publicar
No hace falta una web enorme para equivocarse con hreflang. Los fallos suelen estar en relaciones pequeñas que nadie comprueba después del deploy.
Revisaría lo siguiente.
- Las URLs son absolutas e incluyen
https://. - Cada página se enlaza a sí misma y a todas sus traducciones.
- El conjunto de enlaces es el mismo en ambas versiones.
- El código describe idioma y, solo cuando hace falta, región.
enno es lo mismo queen-GB. - La página enlazada responde correctamente y tiene contenido equivalente en el idioma esperado.
- Cada versión tiene su propia canonical.
- El sitemap no genera una relación distinta a la del HTML.
El último punto es el que más me importa en un sitio generado con código. Copiar etiquetas en una plantilla puede funcionar cuando hay dos páginas. En cuanto hay blog, proyectos y nuevas rutas, el problema pasa a ser mantener la regla sin olvidos.
Por eso, antes de dar esta parte por cerrada, haría un build y abriría una página española y su equivalente inglesa. Miraría el HTML final, no solo el componente de Astro. El navegador y Google reciben lo generado, no la intención que tenía el código fuente.
Lo que hreflang no resuelve
hreflang no decide el idioma de una página por ti. Google detecta el idioma del contenido con sus propios sistemas. Tampoco compensa una traducción pobre, una versión inglesa con cuatro líneas frente a un artículo completo en español o páginas que no responden a la misma intención.
En este blog publico los dos idiomas juntos precisamente para no crear rutas a medias. El inglés no es un botón de traducción pegado al final del proceso. Es otra versión del contenido que necesita título, resumen, enlaces y ejemplos que suenen naturales en inglés.
Esto conecta con algo que aprendí al reconstruir el portfolio con Astro. La estructura no es solo cómo ordenas componentes. También es cómo preparas una web para que cada página tenga una función clara y pueda mantenerse cuando crece.
La implementación de hreflang no es complicada en sí misma. Lo difícil es que siga siendo correcta después de publicar el siguiente artículo, añadir un proyecto o cambiar una ruta. Centralizar la relación entre idiomas es la decisión que me evita convertir ese mantenimiento en una lista de cosas que recordar.