😤
【もっと!早く知りたかった】Next.js(SSR)の際にfirestoreに紐づくsitemap.xmlの作り方
Next.jsにてsitemap.xmlを作る方法はいくつかあります。SSRなのかどうかでやり方は変わってきますが、よく話題になっているのは next-sitemap
だと思います。
CGMとかUGCとかページ数が莫大(動的ページが大きな役割を持つ系のサービス)であると、コンテンツのワードなりでロングテールを拾いたい願望から、sitemap.xmlの意識も高まると思います。
今回はそんな、コンテンツ型のプロダクトをつくっている中でデータベースにfirestoreを使ってしまったパターンのtipsをお話ししていきたいと思います。
SSRをする時は、自作のcomponentでxmlファイルを生成する
僕が実際にやる上で参考にした記事は以下です。
今回やるfirestoreを使ったsitemap.xml作成の肝になる部分は、1document
の中に色々とぶち込んでいく点と考えてます
1documentのMaxサイズは Max document size: 1 MiB (1,048,576 bytes)
とのことなので、結構な数が入ることになります。
1documentで入り切らないようになった時(贅沢な悩み)の対策はまた別の工夫が必要そうですが、一旦はこれで...
ぶちこむとは具体的にはfirestoreのarrayUnionなどを使うことで、documentに紐づいているarrayの中身を取りにいくということです。
ステップ1 Sitemapのモデルを準備する
interface Sitemap {
path: string
lastmod?: string
changefreq: 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly'
priority: number
}
export type { Sitemap }
特に難しいことはありません。
ステップ2 静的ファイルのモデルを準備する
const StaticFields: Sitemap[] = [
{
path: '',
lastmod: new Date().toISOString(),
changefreq: 'hourly',
priority: 1.0,
},
{
path: '/contact',
changefreq: 'monthly',
priority: 0.5,
},
]
ステップ3 APIRoutesでfirestoreから一覧をsitemapを生成するのに必要なパス一覧を取得するAPIを作る
const Api = async (req: NextApiRequest, res: NextApiResponse<any>) => {
switch (req.method) {
case 'GET':
try {
const _ref = doc(firestore, 'sitemap', 'users')
const _snap = await getDoc(_ref)
const _list = _snap.data()
// ここでキャッシュをいれるなどする
res.status(200).json({
users: _list.users,
})
return
} catch {
res.status(400))
}
break
case 'POST':
res.status(400)
break
}
}
ステップ4 firebase-functionsにある、firestoreのトリガーでidを集めるようにする
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { getFirestore, FieldValue } from 'firebase-admin/firestore'
admin.initializeApp()
const firestore = getFirestore()
...
/**
* userが追加された時に
*/
exports.addUser = functions.firestore.document('users/{userId}').onCreate((snap, context) => {
const _id = snap.data().id
if (_id) {
firestore.doc('sitemap/users').update({
paths: FieldValue.arrayUnion(_id),
})
}
})
/**
* userが削除された時に
*/
exports.removeUser = functions.firestore.document('users/{userId}').onDelete((snap, context) => {
const _id = snap.data().id
if (_id) {
firestore.doc('sitemap/users').update({
paths: FieldValue.arrayRemove(_id),
})
}
})
ステップ5 xmlファイルを作る関数を準備する
// 以下URLを参考にした
// https://gladevise.com/next-sitemap-examples
import { Sitemap } from './model'
import { StaticFields } from './static'
const generateSitemapXml = async () => {
const _staticFields = StaticFields
const _url = 'https://.../api/sitemap/user'
const _resp = await fetch(_url)
const _data = await _resp.json()
const _paths = _data.paths as string[]
const _userFields: Sitemap[] = _paths.map((id: string) => ({
path: `user/${id}`,
changefreq: 'weekly',
priority: 0.7,
}))
const fields = _staticFields.concat(_userFields)
let xml = `<?xml version="1.0" encoding="UTF-8"?>`
xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`
fields.forEach(post => {
const url = new URL(post.path, `https://${process.env.MYDOMAIN}`)
xml += `
<url>
<loc>${url.toString().replace(/\/$/, '')}</loc>
<lastmod>${post.lastmod}</lastmod>
<changefreq>${post.changefreq}</changefreq>
<priority>${post.priority}</priority>
</url>
`
})
xml += `</urlset>`
return xml
}
export default generateSitemapXml
ステップ6 xmlファイルを返す/pages/sitemap.xml.tsxをつくる
import { GetServerSideProps } from 'next'
import generateSitemapXml from 'src/framework/sitemap/sitemap'
export const getServerSideProps: GetServerSideProps = async ({ res }) => {
const xml = await generateSitemapXml()
res.statusCode = 200
res.setHeader('Cache-Control', 'public, s-maxage=86400, stale-while-revalidate')
res.setHeader('Content-Type', 'text/xml')
res.end(xml)
return {
props: {},
}
}
const Sitemap = (): void => {
return
}
export default Sitemap
ちょっと大変だけど、作れるのは作れるみたいで安心ですね!
Discussion