Как связать альтернативные языковые версии в React Router v7
Связывайте языковые альтернативы для поисковых систем
Проблема
Когда сайт предоставляет один и тот же контент на нескольких языках, поисковые системы сталкиваются с трудностью. Без явных сигналов они воспринимают каждую языковую версию как отдельную, не связанную страницу. Например, франкоязычный пользователь, ищущий на французском, может увидеть англоязычную версию выше в выдаче, даже если французская версия тоже есть. Аналогично, англоязычный пользователь может попасть на немецкую страницу. Это происходит потому, что поисковые системы не могут автоматически определить, что /en/about и /fr/about — это переводы друг друга, а не конкурирующие страницы с дублирующимся контентом.
Из-за этой путаницы авторитет страницы делится между языковыми версиями, а пользовательский опыт ухудшается. Поисковым системам нужны явные метаданные, чтобы понимать, какие страницы являются языковыми альтернативами одного и того же контента, чтобы показывать подходящую версию в зависимости от языка и местоположения пользователя.
Решение
Добавьте на каждую страницу теги hreflang со списком всех доступных языковых версий этого контента. Эти теги используют атрибут rel="alternate", чтобы указать, что связанные страницы — это переводы, а не дубликаты. Каждый тег указывает языковой код и ведёт на URL соответствующей языковой версии.
Объявляя такие связи в метаданных страницы, вы помогаете поисковым системам понять структуру сайта и показывать каждому пользователю нужную языковую версию. Это повышает релевантность результатов поиска и предотвращает санкции за дублирующийся контент.
Шаги
1. Создайте вспомогательную функцию для генерации ссылок с hreflang
Атрибут hreflang использует языковые коды ISO 639-1, к которым при необходимости добавляется региональный код ISO 3166-1 Alpha 2. Создайте утилиту, которая будет генерировать описания ссылок для всех языковых версий страницы.
type HreflangLink = {
tagName: "link";
rel: "alternate";
hrefLang: string;
href: string;
};
export function buildHreflangLinks(
pathname: string,
locales: string[],
baseUrl: string,
): HreflangLink[] {
return locales.map((locale) => ({
tagName: "link",
rel: "alternate",
hrefLang: locale,
href: `${baseUrl}/${locale}${pathname}`,
}));
}
Эта функция принимает текущий путь, список поддерживаемых локалей и базовый URL вашего сайта, а затем возвращает массив дескрипторов ссылок, которые функция meta может отобразить.
2. Добавьте ссылку x-default для резервного варианта
Значение hreflang x-default указывает на страницу по умолчанию, если ни одна другая страница не подходит лучше, и не нацелено на конкретный язык или локаль. Добавьте это, чтобы направлять пользователей, чей язык не поддерживается.
export function buildHreflangLinks(
pathname: string,
locales: string[],
baseUrl: string,
defaultLocale: string,
): HreflangLink[] {
const links = locales.map((locale) => ({
tagName: "link",
rel: "alternate",
hrefLang: locale,
href: `${baseUrl}/${locale}${pathname}`,
}));
links.push({
tagName: "link",
rel: "alternate",
hrefLang: "x-default",
href: `${baseUrl}/${defaultLocale}${pathname}`,
});
return links;
}
Ссылка x-default обычно ведёт на основную языковую версию вашего сайта и служит резервом для пользователей, чьи языковые предпочтения не совпадают ни с одной из ваших конкретных версий.
3. Экспортируйте ссылки hreflang из вашей meta-функции
Функция meta может задавать теги link на основе данных. Используйте её, чтобы возвращать ссылки hreflang для каждого маршрута.
import type { Route } from "./+types/about";
import { buildHreflangLinks } from "~/utils/hreflang";
const SUPPORTED_LOCALES = ["en", "fr", "de", "es"];
const BASE_URL = "https://example.com";
const DEFAULT_LOCALE = "en";
export function meta({ location }: Route.MetaArgs) {
const hreflangLinks = buildHreflangLinks(
location.pathname,
SUPPORTED_LOCALES,
BASE_URL,
DEFAULT_LOCALE,
);
return [
{ title: "About Us" },
{ name: "description", content: "Learn about our company" },
...hreflangLinks,
];
}
Функция meta возвращает массив дескрипторов, которые могут включать объекты с tagName, установленным в "link". React Router отображает их как элементы link в заголовке документа.
4. Убедитесь, что компонент Meta находится в корневом макете
Компонент Meta рендерит все мета-теги, созданные экспортом meta из модуля маршрута, и должен находиться внутри head вашего документа. Проверьте, что он включён в корневой макет.
import { Links, Meta, Outlet, Scripts } from "react-router";
export default function Root() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
);
}
Компонент Meta собирает и отображает все мета-дескрипторы из совпавшего маршрута, включая теги ссылок hreflang, которые вы определили на шаге 3.
5. Адаптируйте путь для маршрутов с префиксом локали
Если ваши маршруты содержат локаль в пути (например, /en/about), уберите её перед созданием ссылок hreflang, чтобы все языковые версии указывали на одну и ту же логическую страницу.
export function meta({ location }: Route.MetaArgs) {
const pathWithoutLocale = location.pathname.replace(/^\/[a-z]{2}(\/|$)/, "/");
const hreflangLinks = buildHreflangLinks(
pathWithoutLocale,
SUPPORTED_LOCALES,
BASE_URL,
DEFAULT_LOCALE,
);
return [{ title: "About Us" }, ...hreflangLinks];
}
Это гарантирует, что /en/about, /fr/about и /de/about все создают hreflang-ссылки, указывающие на правильные языковые URL для одного и того же контента.