Astroで理想のサイトマップを自作する
Astroには公式のサイトマップインテグレーション( @astrojs/sitemap )があり、npx astro add sitemapを実行するだけでビルド時にサイトマップが生成されます。静的サイトであれば、基本的にはこれで足ります。
この記事はもともと2023年に、当時のインテグレーションにあった「URLに3桁の数字が含まれるページがサイトマップから除外されるバグ」を回避するために書いたものです。そのバグはすでに修正されているため、現在の視点で「それでもサイトマップを自作したいケース」を整理して書き直しました。
当時のバグは修正済み
2023年当時の@astrojs/sitemapは、ステータスコードページ(404や500)を除外するための正規表現が雑で、/blog/123/のような「3桁の数字で終わるURL」まで巻き添えで除外していました。
const STATUS_CODE_PAGE_REGEXP = /\/[0-9]{3}\/?$/; この問題は Issue #10778 として報告され、2024年に修正されました。現在は404・500ページだけが正しく除外されるようになっています。当時このブログはWordPress時代のURL構成(/blog/記事ID/)で運用していたため、もろに直撃していました。
いま自作する理由:SSR(オンデマンドレンダリング)
現在、サイトマップを自作する実質的な理由はこれだと思います。
@astrojs/sitemapはビルド時に静的生成されたページからURLを集める仕組みのため、output: "server"で動くSSRサイトでは動的ルートがサイトマップに入りません。CMSと連携してコンテンツをDBから配信しているようなサイトだと、肝心の記事ページが全部漏れることになります。
このブログ自体がまさにその構成(コンテンツはDBにあり、ページはサーバーレンダリング)なので、サイトマップはビルド時ではなくリクエスト時に生成しています。
SSR用サイトマップエンドポイントの作り方
src/pages/sitemap.xml.tsとしてエンドポイントを作るだけです。
import type { APIRoute } from "astro";
export const prerender = false;
const SITE = "https://example.com";
export const GET: APIRoute = async () => {
// DBやCMSなど、自分のデータソースから公開中のURL一覧を集める
const posts = await getPublishedPosts();
const urls = ["/", "/about", ...posts.map((p) => `/posts/${p.slug}`)];
const xml = [
`<?xml version="1.0" encoding="UTF-8"?>`,
`<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`,
...urls.map((path) => ` <url><loc>${SITE}${path}</loc></url>`),
`</urlset>`,
].join("\n");
return new Response(xml, {
headers: {
"Content-Type": "application/xml; charset=utf-8",
"Cache-Control": "public, max-age=3600",
},
});
}; ポイントは3つです。
prerender = falseを指定して、リクエストのたびにサーバーで生成する(コンテンツの追加・削除が即サイトマップに反映される)Cache-Controlを付けて、クローラーのアクセスのたびに毎回DBを叩かないようにするlastmodを出したい場合は、各コンテンツの更新日時を<url>内に足す
あとはrobots.txtにSitemap: https://example.com/sitemap.xmlの一行を書いておけば完了です。
静的サイトなら公式インテグレーションで十分
逆に、全ページをビルド時に静的生成しているサイトであれば、いま自作する理由はほぼありません。公式インテグレーションにはfilter(除外設定)やcustomPages(手動追加)といったオプションがあるので、細かい調整もそちらで対応できます。
2023年には「バグを回避するため」という後ろ向きな理由で自作していましたが、2026年現在は「SSRだから自作する」という前向きな理由に変わりました。サイトの構成に合わせて選んでください。