🥝
Next.js App RouterとSanityで動的sitemap.xml生成する
概要
Next.jsのサイトを公開するにあたり、サイトマップ生成が必要になると思います。
自動のsitemapジェネレーターのライブラリなどはありますが動的生成ができない等もあり、Next.js公式が出しているやり方をベースにApp Routerで生成するようにしました。
前提
- Next.jsで
App Router
を使用している場合を想定。 - CMSは
Sanity
を使っている場合を想定。(APIリクエストはご自身の環境によって変わりますが基本は一緒です) - 環境変数:
NEXT_PUBLIC_ROOT_URL = "https://siteurl.com"
1.sitemap.xml/route.tsファイルの作成
- App Routerでは
src/app/sitemap.xml/route.ts
ファイルを作成します。 - App Routerでは
getServerSideProps
などは使用できない。
/src/app/sitemap.xml/route.ts
import { NextResponse } from 'next/server';
import { getPosts } from '@/client/sanity/post';
import { Post } from '@/type/sanity.types';
const root = process.env.NEXT_PUBLIC_ROOT_URL;
function generateSiteMap({
posts,
}: {
posts: Post[];
}) {
const staticPages = [
{
url: root,
lastmod: new Date().toISOString(),
changefreq: 'yearly',
priority: 1.0,
},
{
url: `${root}/article`,
lastmod: new Date().toISOString(),
changefreq: 'monthly',
priority: 0.8,
},
];
const dynamicPages = posts.map((post) => ({
url: `${root}/posts/${post.slug?.current}`,
lastmod: post._updatedAt,
changefreq: 'weekly',
priority: 0.5,
}));
const pages = [...staticPages, ...dynamicPages];
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages
.map((page) => {
return `
<url>
<loc>${page.url}</loc>
<lastmod>${page.lastmod}</lastmod>
<changefreq>${page.changefreq}</changefreq>
<priority>${page.priority}</priority>
</url>
`;
})
.join('')}
</urlset>
`;
}
export async function GET() {
// APIリクエストを実行
const posts = await getPosts({ limit: 999 });
const sitemap = generateSiteMap({ posts });
return new NextResponse(sitemap, {
headers: {
'Content-Type': 'application/xml',
},
});
}
2.robots.txtも作成しておく
- App Routerでは
src/app/robots.txt/route.ts
ファイルを作成します。 - 検索エンジンにサイトマップのURLを通知するために必要です。
/src/app/robots.txt/route.ts
import { NextResponse } from 'next/server';
export function GET() {
const sitemapUrl = `${process.env.NEXT_PUBLIC_ROOT_URL}/sitemap.xml`;
const robotsTxt = `
User-agent: *
Sitemap: ${sitemapUrl}
`;
return new NextResponse(robotsTxt, {
headers: {
'Content-Type': 'text/plain',
},
});
}
補足:SanityのAPIクライアント
本件では重要ではないですが補足として書いておきます。
src/app/client/sanity/client.ts
import {createClient} from 'next-sanity'
export const client = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID, // sanity projectID
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET, // sanity dataset name
apiVersion: process.env.NEXT_PUBLIC_API_VERSION || 'v2024-07-17',
})
src/app/client/sanity/post.ts
type PostsProps = {
limit?: number
}
export const POSTS_QUERY = ({limit}: PostsProps) => groq`*[_type == "post"] | order(publishedAt desc)[0...${limit}]{
_id, slug, _updatedAt
}`
export const getPosts = async ({limit = 999}: PostsProps) => {
const posts = await client.fetch<Post[]>(POSTS_QUERY({limit}))
return posts
}
簡単ではありますが、参考になれば幸いです。
Discussion