
Next.jsで静的なXMLサイトマップを作る【外部ライブラリ不要】
2022-04-24
2022-04-24
はじめに
Next.js
廃れないでほしいこふです。
動的なサイト(Twitter
、Zenn
など)ではない静的なサイトにおいて SSG
(Static Site Generation
)時にスクリプトを動かしてサイトマップを作ります。
用途は、以下のような不規則にページが増えないサイトです。
- 個人・企業のブログ
- 個人の日記サイト
環境
Next.js
以外の外部ライブラリは用いません。例えばnextjs-sitemap
というライブラリなど。
1❯ node -v 2v16.14.0
package.json1{ 2 "dependencies": { 3 ... 4 "react": "^17.0.2", 5 "react-dom": "^17.0.2", 6 "next": "^12.1.5", // getStaticPropsが使えるならOK 7 ... 8 }, 9 "devDependencies": { 10 ... 11 "typescript": "4.5.4" 12 ... 13 } 14}
方針
関数を作ってサイトマップの文字列を作成し、ファイルとして書き込む、です。
そのスクリプトは Next.js
の getStaticProps
で動かし、pages/sitemap.tsx
という場所で作成します。
自分の考えるメリット
- ユーザーが見るサイトマップを作る準備ができる
package.json
を変更しなくて良いNext.js
のAPI Routes
を使用しない(Vercel でなくても使える!)
デメリット
- 動的サイトには使えない
ソースコード
src/pages/sitemap.tsx1import { generateSitemapXml } from '@/libs/sitemap' // 下で出てきます 2import { NextPage } from 'next' 3import DefaultErrorPage from 'next/error' 4import Head from 'next/head' 5 6const Page: NextPage = () => { 7 return ( 8 <> 9 <Head> 10 <meta name="robots" content="noindex" /> 11 </Head> 12 <DefaultErrorPage statusCode={404} /> 13 {/* 自分でエラー用のコンポーネント使う */} 14 </> 15 ) 16} 17 18export default Page 19 20export const getStaticProps = async () => { 21 await generateSitemapXml() 22 return { props: {} } 23}
src/lib/sitemap.ts1import { abouts } from '@/constants/footer' 2import { FrontURL } from '@/constants/urls' 3import fs from 'fs' 4 5export const generateSitemapXml = async () => { 6 let xml = `<?xml version="1.0" encoding="UTF-8"?>` 7 xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` 8 const nowJST = new Date( 9 Date.now() + (new Date().getTimezoneOffset() + 9 * 60) * 60 * 1000 10 ) 11 const lastmod = nowJST.toISOString().split('T')[0] 12 13 // about site 14 abouts.forEach((p) => { 15 xml += ` 16 <url> 17 <loc>${FrontURL}${p.href}</loc> 18 <lastmod>${lastmod}</lastmod> 19 <changefreq>monthly</changefreq> 20 </url> 21 ` 22 }) 23 24 // legal 25 ... 26 27 // contact 28 ... 29 30 // blog 31 ... 32 33 xml += `</urlset>` 34 fs.writeFileSync('public/sitemap.xml', xml) 35 return null 36}
ソースコードの解説
sitemap.tsx
1import { generateSitemapXml } from '@/libs/sitemap' // 下で出てきます 2import { NextPage } from 'next' 3import DefaultErrorPage from 'next/error' 4import Head from 'next/head'
必要なライブラリをインポートします。
1const Page: NextPage = () => { 2 return ( 3 <> 4 <Head> 5 <meta name="robots" content="noindex" /> 6 </Head> 7 <DefaultErrorPage statusCode={404} /> 8 {/* 自分でエラー用のコンポーネント使う */} 9 </> 10 ) 11}
大事な点が 2 つあります。
- クローラーに index させない
- 404 エラーのページを見せる(ユーザーのため)
ユーザーが見るサイトマップを作らないなら、です。
もしユーザーも見るサイトマップページ(WordPress
でよくあるやつ)を作るなら、index させ、エラーではなく表示したいコンポーネントを作れば良いだけです。
1export default Page 2 3export const getStaticProps = async () => { 4 await generateSitemapXml() 5 return { props: {} } 6}
ポイントは getStaticProps
でサイトマップを作る関数である generateSitemapXml
を呼び出すということ。
そうすることで package.json
などでコードを追加する必要なく関数をビルド時に一回だけ呼び出すことができます。
sitemap.ts
src/lib/sitemap.ts1import { abouts } from '@/constants/footer' 2import { FrontURL } from '@/constants/urls' 3import fs from 'fs'
必要なライブラリ、定数をインポートします。
1export const generateSitemapXml = async () => { 2 let xml = `<?xml version="1.0" encoding="UTF-8"?>` 3 xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` 4 const nowJST = new Date( 5 Date.now() + (new Date().getTimezoneOffset() + 9 * 60) * 60 * 1000 6 ) 7 const lastmod = nowJST.toISOString().split('T')[0] 8 9 // about site 10 abouts.forEach((p) => { 11 xml += ` 12 <url> 13 <loc>${FrontURL}${p.href}</loc> 14 <lastmod>${lastmod}</lastmod> 15 <changefreq>monthly</changefreq> 16 </url> 17 ` 18 }) 19 20 // legal 21 ... 22 23 // contact 24 ... 25 26 // blog 27 ... 28 29 xml += `</urlset>` 30 fs.writeFileSync('public/sitemap.xml', xml) 31 return null 32}
関数本体です、少し長いですがやってることはシンプルです。
ポイントは以下の通り
xml
の構造にあるように文字列を加算するfs
を使ってファイルとして書き込むpublic
以下に配置する(だれでもアクセスできる場所)
lastmod
ですが、最後に編集した日付としてビルド時を指定しています。
ブログであれば、その情報に紐づいた日付を lastmod
にすれば OK です。
余談ですが、abouts
という変数は別のディレクトリからインポートしています。
どんなやり方であれ、ページのリンクを取得して生成する仕組みがあれば問題ありません。
自分は、値を一箇所で管理するようにしているので、表示名(label
)やリンク先のパス(href
)も別の場所で管理しています。(href
よりpath
の方が良いかも。。。)
1export const abouts: LinksType = [ 2 { 3 label: AboutName, 4 href: AboutPath, 5 }, 6 { 7 label: FaqsName, 8 href: FaqsPath, 9 }, 10 { 11 label: ChangelogName, 12 href: ChangelogPath, 13 }, 14 { 15 label: LoadmapName, 16 href: LoadmapPath, 17 }, 18]
robots.txt
以下のようにクローラーにわかってもらえるよう指定します。
robots.txt1Sitemap: https://example.com/sitemap.xml
おわりに
これでブログ・日記サイトをクローラーに認知してもらう仕組みができました。
src/pages/sitemap.xml
に人間が見るサイトマップを作る時の手間を減らせるのでおすすめです。
これをGoogle Search Console
などで送信してあげれば OK!