Next.js(Pages Router)v16で多言語サイトマップを作成する方法

スケールに対応した言語別サイトマップの整理

問題

サイトマップは、検索エンジンがウェブサイト上のページを発見しインデックス化するのに役立ちます。言語ごとに数百または数千のページを持つ多言語サイトでは、すべてのロケールのすべてのURLをリストアップした単一のサイトマップは、すぐに管理不能になります。大規模なモノリシックサイトマップは、サイトマッププロトコルで定義されている50,000 URLまたは50MBのサイズ制限を超える可能性があり、無効になります。制限内に収まっている場合でも、1つの言語でコンテンツが変更されるたびに巨大なファイルを再生成して検証するのは非効率的です。サイトが成長し、より多くの言語やページが追加されるにつれて、このアプローチはスケールしません。

解決策

サイトマップインデックスファイルを使用して、サイトマップを階層的に整理します。インデックスファイルには、個別の言語固有のサイトマップがリストされ、各サイトマップには単一のロケールのURLが含まれます。この構造により、個々のサイトマップファイルが管理しやすく、プロトコルの制限内に収まります。1つの言語でコンテンツが変更された場合、その言語のサイトマップのみを再生成すればよいのです。このアプローチは、新しい言語が追加されても自然にスケールします。各言語は、インデックスで参照される独自のサイトマップを取得します。検索エンジンは最初にインデックスをクロールし、次に個々の言語サイトマップへのリンクをたどります。

手順

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パラメータからロケールを抽出し、その言語のURLのみを含むXMLを生成します。各サイトマップは単一のロケールのコンテンツに焦点を当てたままになります。

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ディレクトリに配置します。