Next.jsで動的にXMLサイトマップを生成する
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サーバーへの負荷は抑えられます。
pages/sitemap.xml.tsx
を作成する
1. まずpages
ディレクトリ内にsitemap.xml.tsx
という名前のファイルを作成します。/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
を受け取ります。これを使えば、レスポンスの値やヘッダーを自由にコントロールできます。
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
をなんらかの方法で生成します。sitemapやsitemap-generatorなどのnpmライブラリを使うと楽かもしれません。
sitemap.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.txt
はpublic
ディレクトリに配置すればOKです。
↓ こちらもどうぞ。
Discussion