Как связать альтернативные языковые версии в TanStack Start v1

Связывайте языковые альтернативы для поисковых систем

Проблема

Когда сайт предлагает один и тот же контент на нескольких языках, поисковые системы по умолчанию рассматривают каждую языковую версию как отдельную страницу. Без явных сигналов, связывающих эти версии, поисковики не понимают, что /en/about и /fr/about — это переводы одного и того же контента, а не конкурирующие дубликаты. В результате авторитет страницы по поисковому ранжированию делится между языковыми версиями, что приводит к проблемам с отображением: франкоязычный пользователь может увидеть англоязычную версию выше в поисковой выдаче, даже если есть перевод на французский. Поисковым системам нужны явные метаданные, чтобы понять связь между языковыми вариантами и показывать нужную версию в зависимости от языка пользователя и его местоположения.

Решение

Добавьте в head документа элементы ссылки hreflang, которые объявляют все доступные языковые версии каждой страницы. Эти ссылки сообщают поисковым системам, какие URL содержат одинаковый контент на разных языках, что позволяет объединять сигналы ранжирования и показывать пользователям правильную версию. На каждой странице перечисляются все языковые альтернативы, включая ссылку на саму себя, чтобы создать двустороннюю связь, которую поисковики используют для понимания структуры переводов. Эти метаданные добавляются для каждого маршрута с помощью системы управления head фреймворка, которая имеет доступ к текущим параметрам маршрута и может строить URL для всех языковых вариантов.

Шаги

1. Создайте хелпер для генерации альтернативных языковых URL

Метод buildLocation роутера строит полные URL на основе параметров маршрута — его вы будете использовать для генерации URL каждой языковой версии.

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}`,
    };
  });
}

Эта функция принимает текущий путь маршрута и генерирует альтернативные URL, подставляя каждый языковой код в параметры маршрута.

2. Укажите доступные языки

Создайте конфигурационный файл со списком всех языков, которые поддерживает ваше приложение.

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

export const DEFAULT_LANGUAGE = "en";

Этот централизованный список будет использоваться для генерации hreflang-ссылок для каждой страницы.

3. Добавьте hreflang-ссылки в функцию head маршрута

Функция head получает контекст, включая matches, params и loaderData, что даёт доступ к текущему языковому параметру и экземпляру роутера.

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>;
}

Атрибут hreflang использует языковые коды ISO 639-1, и каждая ссылка ведёт на ту же страницу на другом языке.

4. Добавьте x-default hreflang для поведения по умолчанию

Атрибут x-default hreflang указывает страницу по умолчанию, если ни один язык не совпадает.

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,
});

Ссылка x-default предоставляет резервный URL для пользователей, чьи языковые предпочтения не совпадают ни с одной из объявленных альтернатив.

5. Применяйте к динамическим маршрутам с параметрами

Для маршрутов с дополнительными динамическими сегментами помимо языка включайте эти параметры при построении альтернатив.

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,
        },
      ],
    };
  },
});

Каждая страница должна ссылаться на все свои языковые версии, включая саму себя, чтобы поисковые системы понимали полный набор переводов этого контента.

6. Проверьте реализацию hreflang

Функция head возвращает элементы link, которые рендерятся компонентом HeadContent. Проверьте сгенерированный HTML, чтобы убедиться, что ссылки отображаются в теге head документа.

<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" />

Двунаправленные ссылки между страницами позволяют поисковым системам понять связь между локализованными версиями и показывать каждому пользователю наиболее подходящую версию.