Как создать многоязычные карты сайта в Next.js (Pages Router) v16
Организация карт сайта по языкам для масштабирования
Проблема
Карта сайта помогает поисковым системам находить и индексировать страницы сайта. Для многоязычного сайта с сотнями или тысячами страниц на каждом языке одна общая карта сайта, включающая все URL для всех локалей, быстро становится неудобной. Большие монолитные карты сайта могут превышать лимиты протокола карты сайта — 50 000 URL или 50 МБ, из-за чего становятся недействительными. Даже если размер остается в пределах лимита, пересоздавать и проверять огромный файл при каждом изменении контента на одном языке неэффективно. По мере роста сайта и добавления новых языков или страниц такой подход не масштабируется.
Решение
Организуйте карты сайта в иерархию с помощью индексного файла карты сайта. В индексном файле перечисляются отдельные карты сайта для каждого языка, каждая из которых содержит URL только для одной локали. Такая структура позволяет держать отдельные файлы карты сайта в рамках протокольных ограничений. При изменении контента на одном языке нужно пересоздавать только карту сайта для этого языка. Такой подход легко масштабируется — для каждого нового языка создается отдельная карта сайта, которая добавляется в индекс. Поисковые системы сначала обходят индекс, а затем переходят по ссылкам на карты сайта отдельных языков.
Шаги
1. Создайте индексную страницу карты сайта
Создайте страницу в директории pages, чтобы динамически генерировать индекс карты сайта с помощью getServerSideProps.
import { GetServerSideProps } from "next";
const SITE_URL = "https://example.com";
const LOCALES = ["en", "es", "fr", "de"];
function generateSitemapIndex(locales: string[]): string {
const sitemapEntries = locales
.map((locale) => {
return `
<sitemap>
<loc>${SITE_URL}/sitemap-${locale}.xml</loc>
<lastmod>${new Date().toISOString()}</lastmod>
</sitemap>`;
})
.join("");
return `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${sitemapEntries}
</sitemapindex>`;
}
export const getServerSideProps: GetServerSideProps = async ({ res }) => {
const sitemap = generateSitemapIndex(LOCALES);
res.setHeader("Content-Type", "text/xml");
res.write(sitemap);
res.end();
return {
props: {},
};
};
export default function SitemapIndex() {}
В индексе используется корневой элемент <sitemapindex> с записями <sitemap>, каждая из которых содержит дочерний элемент <loc> со ссылкой на карту сайта для определенного языка. Функция getServerSideProps устанавливает заголовок Content-Type в значение text/xml и напрямую записывает XML-ответ.
2. Создайте страницы карты сайта для каждого языка
Создайте страницу с динамическим маршрутом, чтобы генерировать отдельные карты сайта для каждого языка.
import { GetServerSideProps } from "next";
const SITE_URL = "https://example.com";
interface PageData {
slug: string;
lastModified: string;
}
async function getPagesByLocale(locale: string): Promise<PageData[]> {
return [
{ slug: "about", lastModified: "2024-01-15" },
{ slug: "contact", lastModified: "2024-01-20" },
];
}
function generateSitemap(locale: string, pages: PageData[]): string {
const urlEntries = pages
.map((page) => {
return `
<url>
<loc>${SITE_URL}/${locale}/${page.slug}</loc>
<lastmod>${page.lastModified}</lastmod>
</url>`;
})
.join("");
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urlEntries}
</urlset>`;
}
export const getServerSideProps: GetServerSideProps = async ({
params,
res,
}) => {
const locale = params?.locale as string;
const pages = await getPagesByLocale(locale);
const sitemap = generateSitemap(locale, pages);
res.setHeader("Content-Type", "text/xml");
res.write(sitemap);
res.end();
return {
props: {},
};
};
export default function LocaleSitemap() {}
Динамический маршрут извлекает локаль из параметров URL и генерирует XML, содержащий только URL-адреса для этого языка. Каждая карта сайта содержит только контент одной локали.
3. Получите контент для конкретной локали
Замените функцию-заглушку getPagesByLocale на ваш реальный источник данных.
async function getPagesByLocale(locale: string): Promise<PageData[]> {
const response = await fetch(
`https://api.example.com/pages?locale=${locale}`,
);
const data = await response.json();
return data.pages.map((page: any) => ({
slug: page.slug,
lastModified: page.updatedAt,
}));
}
Эта функция обращается к вашей CMS, базе данных или API, чтобы получить страницы для указанной локали. Она возвращает структурированные данные, которые генератор карты сайта преобразует в XML-записи.
4. Добавьте статические страницы в каждую карту сайта
Добавьте статические маршруты, которые есть на всех языках, вместе с динамическим контентом.
function generateSitemap(locale: string, pages: PageData[]): string {
const staticPages = [
{ slug: "", lastModified: new Date().toISOString() },
{ slug: "about", lastModified: new Date().toISOString() },
];
const allPages = [...staticPages, ...pages];
const urlEntries = allPages
.map((page) => {
const path = page.slug ? `/${locale}/${page.slug}` : `/${locale}`;
return `
<url>
<loc>${SITE_URL}${path}</loc>
<lastmod>${page.lastModified}</lastmod>
</url>`;
})
.join("");
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urlEntries}
</urlset>`;
}
Объединяя статические и динамические страницы, вы обеспечиваете полноту карты сайта для каждого языка. Для статических страниц используется текущее время как дата последнего изменения.
5. Укажите индекс в robots.txt
Добавьте расположение индекса карты сайта в файл robots.txt, чтобы поисковые системы могли его найти.
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
Достаточно указать только файл индекса — поисковые системы автоматически перейдут по ссылкам на отдельные языковые карты сайта. Разместите этот файл в директории public.