Как создать мультиязычные карты сайта в 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.