AstroでWebサイトを構築する際、動的ルーティングやISRを活用して、お知らせページ(一覧と詳細)を作成しながら、プレビュー機能を実現する方法を解説します。この記事では、プレビュー用のファイルを別途用意せず、動的ルーティング内で公開されている内容と下書きの内容を切り替える実装方法を紹介します。
※GitHubでソース管理、Vercelでビルド&デプロイすることを想定しています。詳しい設定方法などは【microCMS + Next.jsでJamstackブログを作ってみよう(Next.js 15 ver.)】の記事を参考にしてください。
実装のポイント
動的ルーティングによるページ生成
詳細ページなどを動的ルーティング([id].astro
)で構築し、URLに応じて動的にコンテンツを取得・表示します。この仕組みによりプレビュー用のページ(ファイル)を別途用意せず、1つのファイルで公開中と下書き中の両方に対応できるようにします。
キャッシュ制御
Astro.response
を使用して、キャッシュの制御を行います。通常のURLでアクセスがあった場合はISRによってキャッシュされたページを表示、プレビュー用のURLでアクセスした場合はリアルタイムにデータを取得して表示するようにします。
ISR(Incremental Static Regeneration)
ISRを利用することで、SSGとSSRの長所を組み合わせたページ配信が可能になります。初回アクセス時に生成されたHTMLをキャッシュとして保存し、その後のリクエストでは高速にキャッシュを返します。また、キャッシュが期限切れになると、バックグラウンドで新しいHTMLを再生成して次回以降のリクエストに備えます。この仕組みによりユーザーは常に最新の情報を閲覧できると同時に、サーバー負荷を軽減することができます。
環境構築
今回は以下のツールとライブラリを使用します。
- Astro(v5.0.3)
- node.js(v20.18.1)
- Vercel(デプロイおよびEdge機能)
- microCMS(コンテンツ管理)
- microcms-js-sdk
- Vercelアダプター(@astrojs/vercel)
microCMSのセットアップ
まずはコンテンツを管理するためにmicroCMSでお知らせ(news)というAPIを用意します。
型はリスト形式を選択します。
スキーマにはタイトルと本文を追加します。
APIが用意できたら、後ほど動作確認をするために「公開中の記事」と「下書き中の記事」を作成しておきましょう。
画面プレビューの設定はAPI設定から行います。
動的ルーティングにプレビュー機能も持たせるために遷移先URLはhttps://yourdomain.com/news/{CONTENT_ID}?draftKey={DRAFT_KEY}
とします。yourdomain.com
の部分は環境に合わせて変更してください。
Astroプロジェクトのセットアップ
次に新しいAstroプロジェクトを開始しましょう。
プロジェクト名はastro-isr-preview
としていますが任意の名称で問題ありません。設定もデフォルトもしくはrecommendedを選んでいますが、こちらもお好みで変更してください。
% npm create astro@latest
> npx
> create-astro
astro Launch sequence initiated.
dir Where should we create your new project?
./astro-isr-preview
tmpl How would you like to start your new project?
Include sample files
ts Do you plan to write TypeScript?
Yes
use How strict should TypeScript be?
Strict
deps Install dependencies?
Yes
git Initialize a new git repository?
Yes
✔ Project initialized!
■ Template copied
■ TypeScript customized
■ Dependencies installed
■ Git initialized
next Liftoff confirmed. Explore your project!
Enter your project directory using cd ./astro-isr-preview
Run npm run dev to start the dev server. CTRL+C to stop.
Add frameworks like react or tailwind using astro add.
Stuck? Join us at https://astro.build/chat
╭─────╮ Houston:
│ ◠ ◡ ◠ Good luck out there, astronaut! 🚀
╰─────╯
次にmicroCMSからAstroを呼び出すためにmicrocms-js-sdk
のインストールと環境変数を設定します。
microCMS JavaScript SDKは次のコマンドでインストールすることができます。
$ npm install microcms-js-sdk
環境変数はプロジェクトルート( package.json
が存在する階層)に.env
ファイルを用意することで定義できます。
MICROCMS_SERVICE_DOMAIN=<YOUR_SERVICE> # .microcms.io は含まない値
MICROCMS_API_KEY=<YOUR_KEY_VALUE>
最後にVercelアダプターをインストールしましょう。Vercelアダプターを使用することでサーバーレスやエッジ関数に必要な設定を自動で構築してくれます。(Astro Docs|@astrojs/vercel)
npx astro add vercel
インストールが完了するとプロジェクトルートのastro.config.mjs
にimport vercel from '@astrojs/vercel';
とadapter: vercel()
が追加されます。(追加されなかった場合は手動で追加してください。)
// @ts-check
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel'; // Vercelアダプターの追加
// https://astro.build/config
export default defineConfig({
adapter: vercel() // アダプター設定の追加
});
※Vercelアダプターでisr
にする設定がありますが、今回は静的ページも混在させるためデフォルトのfalse
のまま使用します。
実装手順
1. レンダリングモードについて
Astroのデフォルトのレンダリングモードはoutput: 'static'
です。
Astroの4系ではページをISR対応させるために、output: 'hybrid'
を使用する必要がありましたが、Astro 5.0からハイブリッドモードはstatic
オプションに統合されました。astro.config.mjs
に追加の設定無しでSSGのページとSSRのページをプロジェクト内で共存させることができます。
// @ts-check
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
output: 'static', // 省略可能
adapter: vercel()
});
2. microCMS JavaScript SDKの利用準備をする
src/libs/microcms.ts
を作成します。
import { createClient } from 'microcms-js-sdk';
export const client = createClient({
serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
apiKey: import.meta.env.MICROCMS_API_KEY,
});
3. データを取得するための汎用的な関数を用意する
src/utils/fetchContentOrPreview.ts
にmicroCMSのデータを取得するための汎用的な関数fetchContentOrPreview
を作成しておきましょう。この関数でコンテンツの公開中または下書き中の詳細データを取得します。
import { client } from "../libs/microcms";
// fetchContentOrPreview 関数の引数として受け取るオブジェクトの型を定義
type Params = {
endpoint: string;
id: string;
draftKey?: string | null;
}
// 公開中または下書き中の詳細データを取得する非同期関数
export const fetchContentOrPreview = async <T>({ endpoint, id, draftKey }: Params) => {
try {
// microCMS API から詳細データを取得
return await client.getListDetail<T>({
endpoint: endpoint,
contentId: id,
queries: draftKey ? { draftKey } : {},
});
} catch (error) {
// エラー発生時は null を返す
return null;
}
};
4. 詳細ページの動的ルーティングの設定
お知らせの詳細ページを動的ルーティングにすることを想定しているためsrc/pages/news/[id].astro
を作成します。
デフォルトはSSGですがexport const prerender = false;
を使用することで、SSRに切り替えることができます。
今回は動作確認しやすいようにs-maxage
とstale-while-revalidate
を30秒にしていますが、こちらは運用するサイトにあわせて値を変更してください。
---
import Layout from "../../layouts/Layout.astro";
import { fetchContentOrPreview } from "../../utils/fetchContentOrPreview";
// 型定義
type News = {
id: string;
title: string;
content: string;
};
// サーバーレンダリングにする
export const prerender = false;
// Astro パラメータから id を取得
const { id } = Astro.params;
if (!id) throw new Error("id is required");
// URL から draftKey を取得
const url = new URL(Astro.url.href);
const draftKey = url.searchParams.get("draftKey");
// キャッシュポリシーの定義
const cachePolicies = {
noCache: "no-store, no-cache, max-age=0, must-revalidate",
publicIsr: "public, s-maxage=30, stale-while-revalidate=30",
};
// 詳細データを取得
const content: News | null = await fetchContentOrPreview<News>({
endpoint: "news",
id,
draftKey,
});
// 詳細データがない場合は 404 ページにリダイレクト
if (!content) {
Astro.response.headers.set("Cache-Control", cachePolicies.noCache);
return Astro.redirect("/404", 302);
}
// キャッシュポリシーを設定
const cachePolicy = draftKey ? cachePolicies.noCache : cachePolicies.publicIsr;
Astro.response.headers.set("Cache-Control", cachePolicy);
---
<Layout title={content.title}>
<main>
<h1>{content.title}</h1>
<div set:html={content.content} />
</main>
</Layout>
これにより動的ルーティングへの通常アクセス時とプレビュー時の挙動は下記のようになります。
5. 一覧ページの設定
お知らせの一覧ページとしてsrc/pages/news/[id].astro
を作成します。
このページはビルド時に生成させたいのでexport const prerender = true;
を使用しましょう。
---
import Layout from "../../layouts/Layout.astro";
import { client } from "../../libs/microcms";
// 静的生成(プリレンダリング)にする
export const prerender = true;
// 型定義
type News = {
id: string;
title: string;
content: string;
};
// microCMSのエンドポイント "news" から全ての記事データを取得
const news = await client.getAllContents<News>({
endpoint: "news",
});
---
<Layout title="お知らせ一覧">
<main>
<h1>お知らせ一覧</h1>
<ul>
{
news.map((item) => (
<li>
<a href={`/news/${item.id}`}>{item.title}</a>
</li>
))
}
</ul>
</main>
</Layout>
動作確認してみよう
Vercelでビルド&デプロイして、作成したページにアクセスしてみましょう。
詳しい設定方法などは【microCMS + Next.jsでJamstackブログを作ってみよう(Next.js 15 ver.)】の記事を参考にしてください。
お知らせ一覧
/news
にアクセスするとお知らせの一覧が表示されます。
このページはexport const prerender = true;
の設定によって静的に生成されたページになります。一覧には公開中の記事のみが表示された状態です。
詳細ページ(公開中の記事)
一覧に表示されているリンクまたは/news/{CONTENT_ID}
にアクセスすると記事の詳細が表示されます。
このページはexport const prerender = false;
の設定によってサーバーレンダリングされたページになります。
この時、レスポンスヘッダーに含まれるx-vercel-cache
の値からキャッシュの状態によってレスポンスがどのように処理されたかを確認してみましょう。レスポンスヘッダーはブラウザの開発者ツールなどで確認することができます。
1. 初回アクセス時
デプロイ後に初めてページにアクセスしたとき、x-vercel-cache
は次のようになりました。x-vercel-cache
はMISS
を返しています。これはリクエストされたリソースがキャッシュに存在せず、サーバーから直接取得されたことを示します。取得されたデータはキャッシュとして保存されます。
2. キャッシュの期限内
最初のリクエストから14秒経ってからアクセスした状態です。x-vercel-cache
はHIT
を返しています。これはリクエストされたリソースがCDNキャッシュに存在し、そのキャッシュが有効期限内(今回はs-maxage=30
)であることを示します。このとき、バックグラウンドで生成された最新のキャッシュが返されています。
3. キャッシュの期限切れ
次にキャッシュ生成後、50秒経ってから再度アクセスした状態です。x-vercel-cache
はSTALE
を返しています。これはキャッシュが期限切れになった状態を表しています。この場合、古いキャッシュを一時的に返しつつ、バックグラウンドで新しいキャッシュを生成します。新しいキャッシュを生成すると2からのサイクルに戻ります。
4. 新しいキャッシュ
さらに19秒ほど経ってからアクセスしてみました。x-vercel-cache
はHIT
を返しています。これは先ほどキャッシュが期限切れになった後、新しく生成されたキャッシュから取得された状態です。
5. しばらくリクエストが無かった場合
最後に上記から60秒以上待ってアクセスしてみました。x-vercel-cache
はMISS
を返しています。s-maxage=30
とstale-while-revalidate=30
の期間を超え、キャッシュが古すぎると判断されたためです。この状態ではキャッシュが無効化されており、キャッシュがない状態(初期状態)と同じ扱いになります。
以上によりx-vercel-cache
の値から公開中の記事ページのキャッシュ動作がISRの仕組みになっていることが確認できました。
詳細ページ(記事のプレビュー)
最後にプレビューの動作を確認します。/news/{CONTENT_ID}?draftKey={DRAFT_KEY}
にアクセスすると下書き中の記事が表示されます。
公開中の記事と同様にレスポンスヘッダーに含まれるx-vercel-cache
の値を確認してみましょう。x-vercel-cache
はMISS
を返しています。これはキャッシュに記事が存在せず、サーバーから直接データを取得したことを示します。
公開中の記事では、このタイミングで新しいキャッシュを生成していましたが、プレビューのリクエストではno-store
を設定するようにしたため、取得されたデータはキャッシュとして保存されません。リクエストがある度にサーバーで新しいデータが生成され、最新の内容がレスポンスとして返されます。
これにより記事のプレビューでは常に最新の内容を確認できるようになります。
おわりに
いかがだったでしょうか。
Astroの動的ルーティングにISRの仕組みとプレビュー機能を実現する方法をご紹介いたしました。
ISRの仕組みを導入することによって、新しい記事や更新された内容がバックグラウンドで自動的に反映されるためユーザー体験がより快適になります。また、プレビュー機能を併せて実装することで開発や運用の効率化にもつながると思います。
「ヘッドレスCMSを使ったときにプレビュー用ページってどうやって作るんだろう?」といった疑問をお持ちの方も多いかと思いますので、今回の記事がお役に立てれば幸いです。