🥝

Next.js App RouterとSanityで動的sitemap.xml生成する

2024/10/08に公開

概要

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