Next.jsでサイトマップを作るにはnext-sitemapパッケージを使うとお手軽に作成できる。
しかし、URLごとにlastmodを設定することができなかったり、output: 'export'
が機能しないなどの問題があった。
Next.jsでサイトマップとrobots.txtをnext-sitemapを使って自動生成する
Next.jsのApp Routerではデフォルトでサイトマップを作成するための便利な機能があるのでそれを利用する。
appディレクトリのルートにsitemap.tsを作成する。
その中でurlとlastModifiedを含むオブジェクトの配列を返す関数をデフォルトエクスポートする
export default function sitemap(): MetadataRoute.Sitemap { return [ { url:'https://financial-programmer.net', lastModified:'2023-07-29T17:42:02.812Z' } ];}
これをビルドすると次のようなサイトマップが生成される。
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <script/> <url> <loc>https://financial-programmer.net</loc> <lastmod>2023-07-29T17:42:02.812Z</lastmod> </url></urlset>
urlをひとつひとつ書くのは手間だし、lastModifiedにpage.tsxファイルの更新日時を設定したい。
なので動的に作成できるようにする。
まず、urlオブジェクトを作成するcreateUrlObj関数。
createUrlObj('app/about/page.tsx')
のように使用するとurlと更新日時を取得できる。
import fs from "fs";const baseUrl = 'https://financial-programmer.net';const appDirName = 'app';const pageFileName = 'page.tsx';const createUrlObj = (path: string) => { return { url: baseUrl + path.replace(new RegExp(`${appDirName}|/${pageFileName}`, 'g'), ''), lastModified: fs.statSync(path).mtime };};
replaceを使ってパスから「app」と「/page.tsx」の文字列を除去している。
url: baseUrl + path.replace(new RegExp(`${appDirName}|/${pageFileName}`, 'g'), ''),
statSyncを使うことでファイルの更新日時が取得できる。
lastModified: fs.statSync(path).mtime
次にappディレクトリを探索してpage.tsxが存在していればurlオブジェクトを作成するgetPageInfoList関数。
const getPageInfoList = (pathName: string, list: any[]) => { const dirents = fs.readdirSync(pathName, { withFileTypes: true }); for (const dirent of dirents) { if (dirent.name.startsWith('[') || dirent.name.startsWith('_')) continue; const currentPath = `${pathName}/${dirent.name}`; if (dirent.isDirectory()) getPageInfoList(currentPath, list); if (dirent.name === pageFileName) list.push(createPageInfo(currentPath)); } return list;};
readdirSyncでwithFileTypesオプションを設定するとディレクトリ内のファイルやディレクトリ情報を配列として取得できる。
const dirents = fs.readdirSync(pathName, { withFileTypes: true });
「 [ 」で始まるディレクトリは動的ルーティングなのでここでは無視する。「 _ 」で始まるディレクトリも含めないようにする。
if (dirent.name.startsWith('[') || dirent.name.startsWith('_')) continue;
もし、ディレクトリが存在してたら再帰的に探索させる。
if (dirent.isDirectory()) getPageInfoList(currentPath, list)
もし、page.tsxファイルが存在してたらurlオブジェクトを作成し配列に追加する。
if (dirent.name === pageFileName) list.push(createPageInfo(currentPath));
動的ルーティングがない場合はこれでサイトマップが作成できる。
export default function sitemap(): MetadataRoute.Sitemap { return getUrlObjList(appDirName, []);}
当サイトの場合、https://financial-programmer.net/sitemap.xml
でサイトマップにアクセスできる。
動的ルーティングがある場合は、状況に応じて別に作成する必要がある。
たとえば、mdxファイルを別ディレクトリに保存していてそれで動的ルーティングを作成している場合。
function getBlogUrlObjList() { // mdxのファイル名を配列として取得している。 const blogFiles = getBlogFileNameList(); return blogFiles.map(fileName => { return { url: `${baseUrl}/blog/${fileName.replace(/\.mdx$/, '')}`, lastModified: fs.statSync(`${blogPostDir}/${fileName}`).mtime, }; });};export default function sitemap(): MetadataRoute.Sitemap { return [ ...getUrlObjList(appDirName, []), ...getBlogList() ];}