Next.jsで動的にXMLサイトマップを生成する

4 min read読了の目安(約3700字

2021年1月現在、Next.jsでXMLサイトマップを生成するライブラリとしてはnextjs-sitemap-generatorが最も人気のようです。

nextjs-sitemap-generatorのドキュメントを軽く読む限り、Next.jsのCustom Serverを使用してビルド時にサイトマップを生成する仕組みのようなので、以下のようなケースでの使用には最適とは言えません。

  • Custom Serverは触りたくない
  • コンテンツ追加のタイミングでビルドが走らないユーザー投稿型のサイトでも、サイトマップを一定間隔で更新したい

個人的に色々と試してみたところ、思った以上に簡単にXMLサイトマップを動的に作ることができたので、その方法を共有します。

Next.js + TypeScriptで動的にXML Sitemapを生成する

以下のような方針で実装します。

  • sitemap.xmlをNext.jsの1つのpageとして生成します。API Routesを使っても良いのですが、役割的にpages直下にあった方が分かりやすいと思うので。
  • ビルド時点では生成されておらず、ページへのアクセスがあったときに生成されます。キャッシュするのでAPIサーバーへの負荷は抑えられます。

1. pages/sitemap.xml.tsxを作成する

まずpagesディレクトリ内にsitemap.xml.tsxという名前のファイルを作成します。/sitemap.xmlにアクセスしたときには、このファイルが読み込まれるようになります。

pages/sitemap.xml
import { GetServerSidePropsContext } from 'next';

export const getServerSideProps = async (context: GetServerSidePropsContext) => {
   // この中でサイトマップのコードを生成して返す
};

const Page = () => null;
export default Page;

処理はgetServerSidePropsの中で行うことにします。getServerSidePropsは本来はPageコンポーネント内で使うpropsを取得するためのものですが、今回はXMLを生成して、pageコンポーネントを介さずに直接レスポンスを返却してしまいます。

というのもPageコンポーネント(デフォルトエクスポートするコンポーネント)でいつも通り<?xml version="1.0" encoding="UTF-8"?>などと返しても、_app.tsx_document.tsxの中に埋め込まれることになるため、純粋なxmlファイルにならないからです。言い換えると、レイアウトやheadなどの余計なHTMLが含まれてしまいます。

そのため、Pageコンポーネントではnullを返しておきます。

2. getServerSidePropsの中でxmlを生成して返す

getServerSidePropsでは引数でresを受け取ります。これを使えば、レスポンスの値やヘッダーを自由にコントロールできます。

pages/sitemap.xml
import { GetServerSidePropsContext } from 'next';

export const getServerSideProps = async ({ res }: GetServerSidePropsContext) => {
  const xml = await generateSitemapXml(); // xmlコードを生成する処理(後で書く)

  res.statusCode = 200;
  res.setHeader('Cache-Control', 's-maxage=86400, stale-while-revalidate'); // 24時間のキャッシュ
  res.setHeader('Content-Type', 'text/xml');
  res.end(xml);

  return {
    props: {},
  };
};

const Page = () => null;
export default Page;
  • res.setHeader('Content-Type', 'text/xml')としたうえで、res.end(ここにXMLのコードを入れる)とすれば純粋なXMLファイルを返すことができます。
  • アクセスのたびにサイトマップを生成するのはサーバーへの負荷とパフォーマンスの観点で良くないので、Cache-Controlヘッダーを設定してキャッシュされるようにします。

3. xmlのコードを生成する

あとはxmlをなんらかの方法で生成します。sitemapsitemap-generatorなどのnpmライブラリを使うと楽かもしれません。

sitemap.xmlの書き方がある程度分かっている場合には、自分で書いても良いと思います。

自力でXMLを生成するイメージ
async function generateSitemapXml():string {
  let xml = `<?xml version="1.0" encoding="UTF-8"?>`;
  xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`;
  
  // ここでurlを足していく
  const { posts } = await getAllPosts();
  posts.forEach((post) =>{
    xml += `
      <url>
        <loc>${appHost}${post.path}</loc>
        <lastmod>${lastmod}</lastmod>
        <changefreq>weekly</changefreq>
      </url>
    `
  })
  
  xml += `</urlset>`;
  return xml;
}

自分で書く場合には、以下のページが参考になると思います。

特に日付(lastmod)が適切なフォーマットになっているか、URLが正しい形式になっているか等の点には注意しましょう。

おまけ:サイトマップをSeach Consoleで送信してみる

/sitemap.xmlにアクセスしたときにイメージ通りにXMLサイトマップが表示されるようになったら、Google Search ConsoleにURLを登録しておきましょう。

また、クローラーに対してサイトマップの場所を伝えるには/robots.txtを作成して、Sitemap: http://example.com/sitemap.xmlのような形で、サイトマップのURLを明示しておきます。

詳しくはサイトマップを Google に送信するをどうぞ。

ちなみにNext.jsの場合にはrobots.txtpublicディレクトリに配置すればOKです。


↓ こちらもどうぞ。

https://zenn.dev/catnose99/articles/c7754ba6e4adac

この記事に贈られたバッジ