Многоязычные приложения требуют особого подхода к поисковой оптимизации. Если сайт доступен на нескольких языках, поисковые системы должны понимать:
Даже при корректной настройке тегов hreflang поисковым
системам полезно получать информацию о структуре многоязычного сайта
через Sitemap. Для проектов, использующих I18next, генерация карты сайта
обычно строится на основе того же списка языков, который используется
системой локализации.
Типичная конфигурация I18next содержит список поддерживаемых языков:
i18next.init({
lng: "en",
fallbackLng: "en",
supportedLngs: [
"en",
"de",
"fr",
"es"
]
});
Этот список становится источником данных для генерации многоязычного Sitemap.
Предположим, приложение содержит следующие маршруты:
const routes = [
"/",
"/about",
"/products",
"/contacts"
];
Для каждого маршрута необходимо сформировать URL на всех поддерживаемых языках.
Результат:
https://example.com/en/
https://example.com/en/about
https://example.com/de/
https://example.com/de/about
https://example.com/fr/
https://example.com/fr/about
Таким образом, Sitemap фактически представляет собой произведение:
Маршруты × Языки
Наиболее распространённый вариант:
/en/about
/de/about
/fr/about
Конфигурация I18next:
i18next.init({
supportedLngs: ["en", "de", "fr"]
});
Генерация Sitemap выполняется относительно языковых префиксов.
Другой популярный вариант:
en.example.com
de.example.com
fr.example.com
В этом случае карта сайта содержит разные домены:
<url>
<loc>https://en.example.com/about</loc>
</url>
<url>
<loc>https://de.example.com/about</loc>
</url>
Крупные международные проекты могут использовать отдельные доменные зоны:
example.com
example.de
example.fr
В этом случае генератор Sitemap должен учитывать соответствие языка и домена.
Пример конфигурации:
const domains = {
en: "https://example.com",
de: "https://example.de",
fr: "https://example.fr"
};
Чтобы не дублировать информацию, языки желательно получать непосредственно из конфигурации локализации.
Пример:
export const supportedLanguages = [
"en",
"de",
"fr",
"es"
];
Использование в I18next:
i18next.init({
supportedLngs: supportedLanguages
});
Использование при генерации Sitemap:
import { supportedLanguages } from "./languages.js";
Единый источник данных исключает рассинхронизацию между переводами и SEO-настройками.
Исходные данные:
const languages = [
"en",
"de",
"fr"
];
const routes = [
"",
"about",
"products",
"contacts"
];
Генерация URL:
const urls = [];
for (const lng of languages) {
for (const route of routes) {
urls.push(
`https://example.com/${lng}/${route}`
);
}
}
Полученный массив:
[
"https://example.com/en/",
"https://example.com/en/about",
"https://example.com/de/",
"https://example.com/de/about"
]
Создание элементов Sitemap:
const sitemap = urls
.map(
url => `
<url>
<loc>${url}</loc>
</url>`
)
.join("");
Формирование итогового XML:
const xml = `
<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${sitemap}
</urlset>
`;
Сохранение файла:
import fs from "fs";
fs.writeFileSync(
"public/sitemap.xml",
xml
);
Для многоязычных сайтов рекомендуется указывать альтернативные языковые версии прямо внутри Sitemap.
Пример для страницы:
/en/about
/de/about
/fr/about
XML:
<url>
<loc>https://example.com/en/about</loc>
<xhtml:link
rel="alternate"
hreflang="en"
href="https://example.com/en/about"/>
<xhtml:link
rel="alternate"
hreflang="de"
href="https://example.com/de/about"/>
<xhtml:link
rel="alternate"
hreflang="fr"
href="https://example.com/fr/about"/>
</url>
Такая запись сообщает поисковому роботу, что страницы являются переводами друг друга.
При использовании hreflang необходимо расширить корневой
элемент:
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
Без этого ссылки альтернативных языков будут считаться некорректными.
Исходные данные:
const languages = [
"en",
"de",
"fr"
];
Функция генерации альтернативных ссылок:
function createAlternateLinks(route) {
return languages
.map(
lng => `
<xhtml:link
rel="alternate"
hreflang="${lng}"
href="https://example.com/${lng}/${route}" />`
)
.join("");
}
Формирование узла URL:
function createUrlNode(route) {
return `
<url>
<loc>
https://example.com/en/${route}
</loc>
${createAlternateLinks(route)}
</url>`;
}
В I18next часто используется резервный язык:
i18next.init({
fallbackLng: "en"
});
Обычно английская версия считается основной.
Для неё рекомендуется указывать дополнительное значение:
<xhtml:link
rel="alternate"
hreflang="x-default"
href="https://example.com/en/about"/>
Это сообщает поисковой системе URL по умолчанию.
Генерация:
function createDefaultLink(route) {
return `
<xhtml:link
rel="alternate"
hreflang="x-default"
href="https://example.com/en/${route}" />
`;
}
Не каждая страница доступна на всех языках.
Пример:
const pages = [
{
route: "about",
languages: ["en", "de", "fr"]
},
{
route: "legal",
languages: ["en"]
}
];
Генерация выполняется только для существующих переводов:
for (const page of pages) {
for (const lng of page.languages) {
// создание URL
}
}
Это предотвращает появление в Sitemap страниц с ошибкой 404.
Иногда данные о переводах можно получать непосредственно из структуры локализации.
Пример файлов:
locales
├─ en
│ ├─ common.json
│ └─ pages.json
├─ de
│ ├─ common.json
│ └─ pages.json
└─ fr
└─ common.json
Если отсутствует:
fr/pages.json
страницы соответствующего раздела можно исключить из французской версии Sitemap.
Конфигурация локализации:
module.exports = {
i18n: {
locales: ["en", "de", "fr"],
defaultLocale: "en"
}
};
Получение языков:
const locales = [
"en",
"de",
"fr"
];
Формирование URL:
const urls = locales.flatMap(locale =>
routes.map(
route =>
`https://example.com/${locale}${route}`
)
);
Генерация может выполняться во время сборки проекта.
Создание маршрута:
app.get("/sitemap.xml", (req, res) => {
res.header(
"Content-Type",
"application/xml"
);
res.send(xml);
});
При большом количестве страниц XML может формироваться динамически на основе данных базы.
По стандарту один Sitemap ограничен:
50 000 URL
или
50 МБ
Для крупных многоязычных проектов число страниц быстро растёт.
Пример:
10 000 страниц
×
8 языков
=
80 000 URL
Необходимо разделение:
sitemap-en.xml
sitemap-de.xml
sitemap-fr.xml
Индексный файл:
<sitemapindex>
<sitemap>
<loc>
https://example.com/sitemap-en.xml
</loc>
</sitemap>
<sitemap>
<loc>
https://example.com/sitemap-de.xml
</loc>
</sitemap>
</sitemapindex>
Исходный список языков:
const languages = [
"en",
"de",
"fr"
];
Создание файлов:
for (const lng of languages) {
generateLanguageSitemap(lng);
}
Функция:
function generateLanguageSitemap(lng) {
const urls = routes.map(
route =>
`https://example.com/${lng}/${route}`
);
// формирование XML
}
Подобная схема хорошо масштабируется при увеличении числа языков.
Многие сайты используют данные из базы:
const products = [
{
slug: "iphone-16"
},
{
slug: "galaxy-s25"
}
];
Генерация:
for (const product of products) {
urls.push(
`/products/${product.slug}`
);
}
Для каждого языка создаются собственные URL:
https://example.com/en/products/iphone-16
https://example.com/de/products/iphone-16
https://example.com/fr/products/iphone-16
Иногда переводится не только контент, но и сам путь.
Пример:
/en/products
/de/produkte
/fr/produits
Конфигурация:
const localizedRoutes = {
en: {
products: "products"
},
de: {
products: "produkte"
},
fr: {
products: "produits"
}
};
Генерация:
const route =
localizedRoutes[lng].products;
const url =
`https://example.com/${lng}/${route}`;
Такая структура повышает качество международного SEO.
При добавлении нового языка:
supportedLngs: [
"en",
"de",
"fr",
"it"
]
карта сайта должна пересоздаваться автоматически.
Типичный процесс CI/CD:
1. Добавление переводов
2. Сборка проекта
3. Генерация Sitemap
4. Деплой
Благодаря этому Sitemap всегда соответствует текущей конфигурации I18next.
После генерации необходимо убедиться, что:
hreflang соответствует фактическому языку
страницы;x-default;Согласованность между настройками I18next, маршрутизацией приложения и картой сайта обеспечивает корректную индексацию локализованных страниц и помогает поисковым системам правильно определять языковые версии контента.