next-sitemapでSSRのページだけど静的にサイトマップを生成したい
getServerSidePropsを使っているページでサイトマップを静的に生成したい。
SSGなら何もしなくても簡単に出力してくれるが、SSRだとそうはいかない気がする。
next-sitemapは指定した件数を超えると自動的に分割してファイルを出力してくれる便利な機能がある。この機能を使いたいのでnext-sitemapに則ったルールで生成を行いたい。(サイトマップは1つのファイルに50,000件のみ記載できる。それ以上はファイル分割しなければならない。)
SSRのページなのにサイトマップを静的生成する経緯
SSGで120,000ページの生成を行っていたのですが、デプロイの際に問題が発生してしまいました。そのため、無理にSSGをするのではなく、SSRを行うことにしました。今回のSSRの場合だと、ユーザー投稿される訳ではないので、既にパスが分かっている状態なので、サイトマップを静的に生成をするということになります。(近いものとしてブログのリニューアルで、記事をSSGするのではなくSSRするみたいなイメージですかね。)
本来、ユーザー投稿が含まれる部分のサイトマップならば、動的生成でサイトマップを出力するものだと思います。
今回は npx next-sitemap
を叩いた時点での実行を行う想定です。
ちなみにSSGの場合は
SSGの場合は何もすることなく生成されたファイルを勝手に見ていい感じにサイトマップを生成してくれます。脳みそ動かさなくてOKです。
next-sitemap-config.js に関数を作ればOK!
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: process.env.SITE_URL || 'https://hoge.jp/',
generateRobotsTxt: true,
outDir: './public/hogehoge',
additionalPaths: async () => { // ポイントはここ!任意のパスを追加する処理を行っている
return await generateSamplePaths()
}
}
// ここでパスを生成する
const generateSamplePaths= async () => {
const appRoot = require('app-root-path')
const fs = require('fs/promises')
const jsonBlob = await fs.readFile(`${appRoot}/src/json/hoge.json`, 'utf8')
const hogeJSON = await JSON.parse(jsonBlob)
const result = []
for (let key in hogeJSON) {
result.push({
loc: `/sample/${key}`, // この値は必須(パスを生成)
changefreq: 'daily',
priority: 0.8,
lastmod: new Date().toISOString()
})
}
return result
}
loc以外にもchangefreqやpriorityやlastmodを追加できますが、
この値はnext-sitemapのデフォルト値に任せたいと思います。
getStaticPaths を作る感覚で実装できるのでチャレンジしてみてください!
outDir使うときはちょっと工夫が必要
上のサンプルでは訳あって next-sitemap.config.js
の outDir
でサイトマップの出力先を変更しています。このままでは使えないので調整が必要です。
const replaceSitemap = async (fileName) => {
const fs = require('fs/promises')
const appRoot = require('app-root-path')
const subDirectory = 'hogehoge'
const filePath = `${appRoot}/public/${subDirectory}/${fileName}`
const original = await fs.readFile(filePath, 'utf8')
const replacedData = original.replace(
/https\:\/\/hoge\.jp\/sitemap/g,
`https://hoge.jp/${subDirectory}/sitemap`
)
await fs.writeFile(filePath, replacedData, 'utf8')
}
;(async () => {
await replaceSitemap('robots.txt')
await replaceSitemap('sitemap.xml')
})()
こんな感じの関数を書いておき、 package.json の postbuild
を next-sitemap && node sitemap-replace.js
としておけば調整完了です。
なぜ調整が必要なのか?
next-sitemap では対応していない部分があります。サイトマップの表紙的な役割を持つ sitemap.xml
や robots.txt
で outDir に適した形に変更してくれないのです。
outDir で指定した出力先を向いてくれないので、せっかく生成したサイトマップを Googleのクローラーが読み込んでくれない現象が発生します。
というのも、サイトマップの表紙的な役割を持つ sitemap.xml
で出力される分割サイトマップへのリンクが適切に変更されていないのです。また、robots.txtのSitemapという箇所も変更されていません。
このままではサイトマップが存在しない扱いになってしまい意味がありません。なので、 sitemap-replace.js を実行させて適切な形にサイトマップの場所を書き換えているということです。
対応面倒くさいのに outDir を変更する理由は?
不適切なスクレイピング対策です。
スクレイピングをする人はサイトマップを探すこともあります。サイトマップさえ見つけてしまえば、サイト内のスクレイピングの難易度がめちゃくちゃ下がります。スクレイパーに知られる必要はありませんし、Googleさんだけに教えてあげればいいのですから、普通では探せない場所に設置しておく方が好ましいでしょう。