現象について
タイトルのような現象が起きてしまったので、その解消法を記録しておきます。なお、環境は以下の通りです。
- astro: 5.1.7
- @astrojs/vercel: 8.0.2
- @astrojs/sitemap: 3.2.1
はじめに Astro での SSR の運用方法について書くので現象の解決方法が知りたい方はこちらまで飛んでください。
Astro での運用
今までは Next.js を使ってサイトを構築していましたが、Astro を見つけてからは使いやすさにこちらを使ってサイト運営をしています。
デプロイは Vercel を使っているのですが、始めは元から設定されている SSG(Static Site Generator:静的サイトジェネレーター)として運用をしていました。
ただ、どうしても GET メソッドを使いたい場面が出てきたため、一部を SSR(Sever Side Rendring:サーバーサイドレンダリング)で運用することにしました。
Astro での SSR の設定
Astro で SSR をするためには、全体を始めからの設定である SSG のままで一部を SSR にするか、全体を SSR にして一部を SSG にするか、の選択が必要になります。(もちろんすべて SSR にすることも可能)私は前者を選びました。設定方法は公式サイトのOn-demand renderingにありますが、簡単に言うと@astrojs/vercel アダプターをインストールすることで SSR を使用できるようにします。
ます、
shell
1$ npx astro add vercel
を実行します。
shell
1 ╭──────────────────────╮2 │ npm install @astrojs/vercel@^8.0.2 │3 ╰──────────────────────╯4 Continue?(Y/n)
と@astrojs/vercel アダプターをインストールするが
聞かれるので yes
その上で astro.config.mjs に
shell
1 ╭ astro.config.mjs ───────────────────╮2 │import { defineConfig } from 'astro/config'; │3 │import vercel from '@astrojs/vercel/serverless'; │4 │ │5 │export default defineConfig({ │6 │ adapter: vercel(), │7 │}); │8 ╰────────────────────────────╯9 Continue?(Y/n)
が追加されるがいいか聞かれるので yes
を入力すると@astrojs/vercel アダプターが使用できるようになり SSR が使える状態になります。
全体を SSG のままで一部を SSR にする場合
この場合はとても簡単です。SSR にしたいファイルに
text
1---astro2export const prerender = false3---4<html>5<p>このページはSSRです。</p>6<html>
を追加するだけで、そのページが SSR になります。
全体を SSR にして一部を SSG にする場合
この場合は astro.config.mjs に手を加えます。
typescript
1import { defineConfig } from 'astro/config';2import vercel from '@astrojs/vercel/serverless';34export default defineConfig({5 output: 'server',6 adapter: vercel(),7});
を追加します。これで、すべてが SSR になりました。
あとは、SSG にしたいファイルに
astro
1---2+ export const prerender = true3---4<html>5<p>このページはSSGです。</p>6<html>
とすればできあがります。
サイトマップの設定
Astro でサイトマップを設定するのは非常に簡単です。@astrojs/vercel アダプターをインストールしたように@astrojs/sitemapを参考に
shell
1$ npx astro add sitemap
を実行します。
shell
1 ╭────────────────────────╮2 │ npm install @astrojs/sitemap@^3.2.1 │3 ╰────────────────────────╯4 Continue?(Y/n)
と@astrojs/sitemap をインストールするが聞かれるので yes
その上で astro.config.mjs に
shell
1 ╭ astro.config.mjs ───────────────────╮2 │import { defineConfig } from 'astro/config'; │3 │import vercel from '@astrojs/vercel/serverless'; │4 │import sitemap from "@astrojs/sitemap"; │5 │ │6 │export default defineConfig({ │7 │ integrations: [sitemap()], │8 │ adapter: vercel(), │9 │}); │10 ╰────────────────────────────╯11 Continue?(Y/n)
が追加されるがいいか聞かれるので yes
を入力すると下準備の完了です。
このあともう少し手を加えないといけません。引き続き astro.config.mjs に
typescript
1import { defineConfig } from 'astro/config';2import vercel from '@astrojs/vercel/serverless';3import sitemap from "@astrojs/sitemap";45export default defineConfig({6 site: 'https://<自分のサイトアドレス>',7 integrations: [sitemap()],8 adapter: vercel(),9});
を記入して build すると sitemap-index.xml と sitemap-0.xml が作成されます。
Vercel ヘのデプロイ
満を持してできあがったファイルをコミットし、GitHub にプッシュして、Vercel でデプロイしました。
その後で、 を確認してみると、「404 Not found」の表示。「はて、設定を間違えたか?」色々試しデプロイし直しますが、どうやっても sitemap が見つかりません。
text
1https://<自分のサイトアドレス>/sitemap-index.xml
ほとほと困っていたところ検索に「@astrojs/sitemap excluded from final static output with Vercel adapter(訳:@astrojs/sitemap は、Vercel アダプターで最終的な静的出力から除外されます。)」という内容の issue を発見。
どうにも、今の@astrojs/vercel のバージョンだと@astrojs/sitemap プラグインが、Vercel がすでに静的ファイルの出力を終えた後に sitemap を生成する設定になっているため、結果的にsitemap が静的ファイルから除外されてしまう問題があるのだそうです。
解決方法
さて、その解決方法ですが、上の issueにありました。mohdlatif さんの 2024/12/28の書き込みを参考に
まず、フォルダーのトップに copy-files.ts という名前で以下の内容のファイルを作成します。
typescript
1import type { AstroIntegration } from 'astro';2import { promises as fs } from 'fs';3import path from 'path';4import { fileURLToPath } from 'url';56// フォーマットされたログ記録のためのユーティリティ関数7function formatLog(tag: string, message: string) {8 const timestamp = new Date().toLocaleTimeString('en-US', {9 hour: '2-digit',10 hour12: false,11 minute: '2-digit',12 second: '2-digit'13 });1415 // 下の行はeslintのno-consoleエラー対策のため16 // eslint-disable-next-line no-console17 console.log(18 '\n' + // 上にスペースを追加19 `\x1b[90m${timestamp}\x1b[0m ` + // グレーのタイムスタンプ20 `[\x1b[36m${tag}\x1b[0m] ` + // シアン色のタグ21 `${message}` + // メッセージ22 '\n' // 下にスペースを追加23 );24}2526// ファイルを静的フォルダに移動する27async function copyFiles(srcDir: string, destDir: string) {28 const files = await fs.readdir(srcDir);2930 for (const file of files) {31 const srcPath = path.join(srcDir, file);32 const destPath = path.join(destDir, file);3334 const stat = await fs.stat(srcPath);3536 if (stat.isDirectory()) {37 await fs.mkdir(destPath, {recursive: true});38 await copyFiles(srcPath, destPath);39 } else {40 await fs.copyFile(srcPath, destPath);41 }42 }43}4445// コピー実行フック関数46export function CopyFilesPlugin(): AstroIntegration {47 return {48 hooks: {49 'astro:build:done': async ({dir}) => {50 formatLog('copy-files', 'Copying files to .vercel/output/static');5152 const distDir = fileURLToPath(dir.href);53 const staticDir = path.resolve('.vercel/output/static');5455 await fs.mkdir(staticDir, {recursive: true});56 await copyFiles(distDir, staticDir);57 }58 },59 name: 'copy-files'60 };61}62その後 astro.config.mjs に以下を書き込みます6364import { defineConfig } from 'astro/config';65import vercel from '@astrojs/vercel/serverless';66import sitemap from "@astrojs/sitemap";67import {CopyFilesPlugin} from './copy-files';6869export default defineConfig({70 site: 'https://<自分のサイトアドレス>',71 integrations: [72 sitemap()73 // 他のintegrationsをすべて書き終えた一番最後に記入する74 CopyFilesPlugin()75 ],76 adapter: vercel(),77});
これでコミット、プッシュ、デプロイでちゃんと sitemap が表示できるようになりました。
