Cómo enlazar versiones de idiomas alternativos en TanStack Start v1

Enlazar alternativas de idioma para motores de búsqueda

Problema

Cuando un sitio web ofrece el mismo contenido en varios idiomas, los motores de búsqueda tratan cada versión de idioma como una página separada por defecto. Sin señales explícitas que conecten estas versiones, los motores de búsqueda no pueden entender que /en/about y /fr/about son traducciones del mismo contenido en lugar de duplicados que compiten entre sí. Esta fragmentación divide la autoridad de clasificación entre las versiones de idioma y crea problemas de servicio: un usuario de habla francesa podría ver la versión en inglés clasificada más alta en los resultados de búsqueda, aunque exista una traducción en francés. Los motores de búsqueda necesitan metadatos explícitos para entender la relación entre las variantes de idioma y servir la versión apropiada basada en las preferencias de idioma del usuario y su ubicación.

Solución

Añadir elementos de enlace hreflang al encabezado del documento que declaren todas las versiones de idioma disponibles de cada página. Estos enlaces indican a los motores de búsqueda qué URLs contienen el mismo contenido en diferentes idiomas, permitiéndoles consolidar las señales de clasificación y servir la versión correcta a los usuarios. Cada página enumera todas sus alternativas de idioma, incluyendo una referencia a sí misma, creando una relación bidireccional que los motores de búsqueda utilizan para entender la estructura de traducción. Estos metadatos se añaden por ruta utilizando el sistema de gestión de encabezados del framework, que tiene acceso a los parámetros de la ruta actual necesarios para construir URLs para todas las variantes de idioma.

Pasos

1. Crear un helper para construir URLs alternativas de idioma

El método buildLocation del router construye URLs completas a partir de los parámetros de ruta, que utilizarás para generar URLs para cada versión de idioma.

import { AnyRouter } from "@tanstack/react-router";

export function buildLanguageAlternates(
  router: AnyRouter,
  currentPath: string,
  currentLang: string,
  availableLanguages: string[],
) {
  return availableLanguages.map((lang) => {
    const location = router.buildLocation({
      to: currentPath,
      params: { lang },
    });
    return {
      lang,
      href: `${location.pathname}${location.search}${location.hash}`,
    };
  });
}

Esta función toma la ruta actual y genera URLs alternativas sustituyendo cada código de idioma en los parámetros de la ruta.

2. Define tus idiomas disponibles

Crea un archivo de configuración que enumere todos los idiomas que tu aplicación soporta.

export const AVAILABLE_LANGUAGES = ["en", "fr", "de", "es"];

export const DEFAULT_LANGUAGE = "en";

Esta lista centralizada se utilizará para generar enlaces hreflang para cada página.

3. Añade enlaces hreflang en la función head de una ruta

La función head recibe un contexto que incluye matches, params y loaderData, lo que proporciona acceso al parámetro de idioma actual y a la instancia del router.

import { createFileRoute } from "@tanstack/react-router";
import { buildLanguageAlternates, AVAILABLE_LANGUAGES } from "../i18n-config";

export const Route = createFileRoute("/$lang/about")({
  head: ({ params }) => {
    const alternates = buildLanguageAlternates(
      Route.router,
      "/$lang/about",
      params.lang,
      AVAILABLE_LANGUAGES,
    );

    return {
      links: alternates.map((alt) => ({
        rel: "alternate",
        hreflang: alt.lang,
        href: alt.href,
      })),
    };
  },
  component: AboutPage,
});

function AboutPage() {
  return <div>About page content</div>;
}

El atributo hreflang utiliza códigos de idioma ISO 639-1, y cada enlace apunta a la misma página en un idioma diferente.

4. Añade un hreflang x-default para el comportamiento de respaldo

El atributo hreflang x-default indica la página predeterminada cuando ningún idioma coincide.

export const Route = createFileRoute("/$lang/about")({
  head: ({ params }) => {
    const alternates = buildLanguageAlternates(
      Route.router,
      "/$lang/about",
      params.lang,
      AVAILABLE_LANGUAGES,
    );

    const defaultUrl = alternates.find((alt) => alt.lang === "en");

    return {
      links: [
        ...alternates.map((alt) => ({
          rel: "alternate",
          hreflang: alt.lang,
          href: alt.href,
        })),
        {
          rel: "alternate",
          hreflang: "x-default",
          href: defaultUrl?.href || alternates[0].href,
        },
      ],
    };
  },
  component: AboutPage,
});

El enlace x-default proporciona una URL de respaldo para los usuarios cuyas preferencias de idioma no coinciden con ninguna alternativa declarada.

5. Aplicar a rutas dinámicas con parámetros

Para rutas con segmentos dinámicos adicionales más allá del idioma, incluye esos parámetros al construir alternativas.

export const Route = createFileRoute("/$lang/posts/$postId")({
  head: ({ params }) => {
    const alternates = AVAILABLE_LANGUAGES.map((lang) => {
      const location = Route.router.buildLocation({
        to: "/$lang/posts/$postId",
        params: { lang, postId: params.postId },
      });
      return {
        lang,
        href: `${location.pathname}${location.search}${location.hash}`,
      };
    });

    return {
      links: [
        ...alternates.map((alt) => ({
          rel: "alternate",
          hreflang: alt.lang,
          href: alt.href,
        })),
        {
          rel: "alternate",
          hreflang: "x-default",
          href:
            alternates.find((a) => a.lang === "en")?.href || alternates[0].href,
        },
      ],
    };
  },
});

Cada página debe hacer referencia a todas sus versiones de idioma, incluida ella misma, asegurando que los motores de búsqueda comprendan el conjunto completo de traducciones para ese contenido.

6. Verificar la implementación de hreflang

La función head devuelve elementos de enlace que son renderizados por el componente HeadContent. Inspecciona el HTML renderizado para confirmar que los enlaces aparecen en el encabezado del documento.

<link rel="alternate" hreflang="en" href="/en/about" />
<link rel="alternate" hreflang="fr" href="/fr/about" />
<link rel="alternate" hreflang="de" href="/de/about" />
<link rel="alternate" hreflang="es" href="/es/about" />
<link rel="alternate" hreflang="x-default" href="/en/about" />

Los enlaces bidireccionales entre páginas aseguran que los motores de búsqueda entiendan la relación entre versiones localizadas, permitiéndoles servir la versión más apropiada a cada usuario.