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

スケールのために言語別にサイトマップを整理する

問題

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

解決策

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

ステップ

1. サイトマップインデックスページを作成する

getServerSidePropsを使用してサイトマップインデックスを動的に生成するページをpagesディレクトリに作成します。

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ディレクトリに配置してください。