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

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

Проблема

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

Решение

Добавьте элементы ссылки hreflang в заголовок документа, чтобы указать все доступные языковые версии каждой страницы. Эти ссылки сообщают поисковым системам, какие URL содержат один и тот же контент на разных языках, позволяя им объединять сигналы ранжирования и показывать правильную версию пользователям. Каждая страница перечисляет все свои языковые альтернативы, включая ссылку на саму себя, создавая двустороннюю связь, которую поисковые системы используют для понимания структуры переводов. Эти метаданные добавляются для каждого маршрута с использованием системы управления заголовками фреймворка, которая имеет доступ к текущим параметрам маршрута, необходимым для построения 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>Содержимое страницы "О нас"</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, чтобы убедиться, что ссылки отображаются в заголовке документа.

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

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