Astro + Cloudflare WorkersでOn-demand ISRみたいなことをする
はじめに
Astro 5.14 と Cloudflare Workers を使って On-demand ISR みたいなことをする記事です
構成
- Astro 5.14
- Node 25.0.0
完成品
GitHub リポジトリはこちら
実際に動作するデモも用意しました
On-demand ISR とは?
以下、Gemini 2.5 Flash に生成してもらいました。
Next.js で使われる、Web サイトの表示速度と情報更新の速さを両立する仕組みです。
ざっくり言うと、「普段は速いキャッシュを使いつつ、コンテンツを更新したら、すぐに(オンデマンドで)最新版に切り替えられる技術」です。
仕組みのポイント
| 要素 | 従来の SSG/ISR との違い | メリット |
|---|---|---|
| ISR (Incremental Static Regeneration) | あらかじめページを生成し(SSG)、一定時間後に更新がないかチェックする。 | サーバー負荷を抑え、高速表示を実現。 |
| On-demand | コンテンツ管理システム (CMS) などから「今すぐ更新して!」と**指示(リクエスト)**を送れる。 | 時間待ちなしで、更新直後にサイトへ最新情報を反映できる。 |
導入のメリット
- 最速の表示速度:事前に生成されたページを使うため、常に表示が速い。
- 即時反映:記事を公開・更新したら、ほぼリアルタイムでユーザーに最新情報が届く。
- サーバー負荷軽減:アクセスがあるたびにページを作る必要がない。これにより、高速性と最新性を両立した、ユーザー体験の良いブログ運営が可能になります。
実装
1. Astro プロジェクトの作成
Astro プロジェクトを作成していきます
Cloudflare Workers を使うので公式のコマンドを使って作成しました
npm create cloudflare@latest -- <プロジェクト名> --framework=astro
今回は最小限の構成で進めるために、テンプレート無しでインストールしています
2. Astro の設定変更
astro のコンフィグファイルをいじって、オンデマンド でレンダリングするように変更します
// @ts-check
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
// https://astro.build/config
export default defineConfig({
adapter: cloudflare({
platformProxy: {
enabled: true,
},
imageService: "cloudflare",
}),
+ output: "server"
});
必ずしも output: server にする必要はありませんが、output を static(基本 SSG で生成するやつ)にする場合は、On-demand ISR で動作させたいページのみ export const prerender = false で SSR にしてください
3. Cloudflare Workers のエントリーファイルの作成
Cloudflare Workers ではオンデマンドレンダリングの場合デフォルトで HTML はキャッシュしてくれないので、HTML をキャッシュしてくれるように Cloudflare Cache API 経由で設定します
今回は適当にキャッシュ時間を 1 年にしていますが、ここはお好みで変更してください
作成したエントリーファイルを Wrangler の設定ファイルで読み込めるように編集します
4. CDN のキャッシュをパージする処理の作成
Cloudflare API を使ってキャッシュをパージする処理を作成します
今回はわかりやすさのためにボタンを押したらキャッシュをパージする処理を Astro Actions を用いて作っています
やっていることは単純で、該当の API に対して削除したいページの URL をボディに含めて POST するだけです
Purge Cache については以下の公式ドキュメントを参考にしてください
とはいえ Astro Actions での実装は実用的なものではないので、実際に運用することを考えると Astro の API エンドポイントを作成する形になるかなと
Astro にもカスタムエンドポイントを作成する機能が備わっているので、Astro だけで開発する必要がある場合にはこの機能を使って実装するのが良いのではないでしょうか
または AWS の Lambda や Google の Cloud Functions などで実装することも有効です
仮に Astro のカスタムエンドポイントを使用して実装する場合は、以下のようなコードで実装できると思います(未検証)
import type { APIRoute } from "astro";
type PurgeRequest = {
file: string;
};
export const POST: APIRoute = async ({ request }) => {
const body = await request.json<PurgeRequest>();
try {
const response = await fetch(
`https://api.cloudflare.com/client/v4/zones/${process.env.CLOUDFLARE_ZONE_ID}/purge_cache`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
files: [body.file],
}),
}
);
if (!response.ok) {
return new Response(JSON.stringify({ message: "Failed to purge cache" }), {
status: 500,
});
}
return new Response(JSON.stringify({ message: "Purge request received" }), { status: 200 });
} catch (error) {
return new Response(JSON.stringify({ message: "Internal server error" }), { status: 500 });
}
};
動作確認
以下のようにボタンを設置した簡単なページを作成します
ページにアクセスして、動作をチェックしてみます
ボタンを押すと日時が更新されましたね
ボタンを押さずにページを更新したら日時はそのままになっていることが確認できます(動画は撮ってないですが)
これをヘッドレス CMS などで使用するとしたら、
- ヘッドレス CMS でブログ記事を更新する
- Webhook を
/api/purge/に飛ばすように登録する - 受信した Webhook から URL を抽出して Cloudflare API に送信する
- Cloudflare がキャッシュをパージし、来訪者は最新の情報を見ることができる
といった流れになるかなと思います
注意点
Zone ID と一致するドメインしかキャッシュのパージは不可能です
*.workers.dev のキャッシュをパージしようとしたら全くパージされませんでした
また、ヘッドレス CMS で管理されていないページに関しては手動でパージするくらいしか自分には方法を思いつかないので、あくまで更新時に Webhook が送信される外部サービスを使用しているページ限定かなと思います
まあそういうページはおそらく SSR ではなく SSG でレンダリングするのが良いんだろうなと思いますが
終わりに
今回は Astro と Cloudflare Workers で On-demand ISR っぽいことをする記事を書いてみました
機会があれば実際に microCMS と接続して、ブログ記事更新時にキャッシュパージしてみたり記事をたくさん用意してビルド時間とかにどれだけ差があるのか記事を書いてみようと思います
この記事がみなさんの参考になれば幸いです

Discussion