🎦

Astro + Cloudflare WorkersでOn-demand ISRみたいなことをする

に公開

はじめに

Astro 5.14 と Cloudflare Workers を使って On-demand ISR みたいなことをする記事です

構成

  • Astro 5.14
  • Node 25.0.0

完成品

GitHub リポジトリはこちら

https://github.com/shibaTT/ondemand-isr-astro

実際に動作するデモも用意しました

https://ondemand.test.butter-s.com/

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 のコンフィグファイルをいじって、オンデマンド でレンダリングするように変更します

astro.config.mjs
// @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 経由で設定します

https://github.com/shibaTT/ondemand-isr-astro/blob/main/src/worker.js

今回は適当にキャッシュ時間を 1 年にしていますが、ここはお好みで変更してください

https://github.com/shibaTT/ondemand-isr-astro/blob/main/src/worker.js#L43-L43

作成したエントリーファイルを Wrangler の設定ファイルで読み込めるように編集します

https://github.com/shibaTT/ondemand-isr-astro/blob/main/wrangler.jsonc#L8-L8

4. CDN のキャッシュをパージする処理の作成

Cloudflare API を使ってキャッシュをパージする処理を作成します

今回はわかりやすさのためにボタンを押したらキャッシュをパージする処理を Astro Actions を用いて作っています

https://github.com/shibaTT/ondemand-isr-astro/blob/main/src/actions/index.ts

やっていることは単純で、該当の API に対して削除したいページの URL をボディに含めて POST するだけです
Purge Cache については以下の公式ドキュメントを参考にしてください

https://developers.cloudflare.com/cache/how-to/purge-cache/
https://developers.cloudflare.com/cache/how-to/purge-cache/purge-zone-versions/

とはいえ Astro Actions での実装は実用的なものではないので、実際に運用することを考えると Astro の API エンドポイントを作成する形になるかなと

Astro にもカスタムエンドポイントを作成する機能が備わっているので、Astro だけで開発する必要がある場合にはこの機能を使って実装するのが良いのではないでしょうか

https://docs.astro.build/ja/guides/endpoints/

または AWS の Lambda や Google の Cloud Functions などで実装することも有効です

仮に Astro のカスタムエンドポイントを使用して実装する場合は、以下のようなコードで実装できると思います(未検証)

src/pages/api/purge.ts
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 });
    }
};

動作確認

以下のようにボタンを設置した簡単なページを作成します

https://github.com/shibaTT/ondemand-isr-astro/blob/main/src/pages/index.astro

ページにアクセスして、動作をチェックしてみます

https://ondemand.test.butter-s.com/

Image from Gyazo

ボタンを押すと日時が更新されましたね
ボタンを押さずにページを更新したら日時はそのままになっていることが確認できます(動画は撮ってないですが)

これをヘッドレス CMS などで使用するとしたら、

  1. ヘッドレス CMS でブログ記事を更新する
  2. Webhook を /api/purge/ に飛ばすように登録する
  3. 受信した Webhook から URL を抽出して Cloudflare API に送信する
  4. Cloudflare がキャッシュをパージし、来訪者は最新の情報を見ることができる

といった流れになるかなと思います

注意点

Zone ID と一致するドメインしかキャッシュのパージは不可能です
*.workers.dev のキャッシュをパージしようとしたら全くパージされませんでした

また、ヘッドレス CMS で管理されていないページに関しては手動でパージするくらいしか自分には方法を思いつかないので、あくまで更新時に Webhook が送信される外部サービスを使用しているページ限定かなと思います
まあそういうページはおそらく SSR ではなく SSG でレンダリングするのが良いんだろうなと思いますが

終わりに

今回は Astro と Cloudflare Workers で On-demand ISR っぽいことをする記事を書いてみました

機会があれば実際に microCMS と接続して、ブログ記事更新時にキャッシュパージしてみたり記事をたくさん用意してビルド時間とかにどれだけ差があるのか記事を書いてみようと思います

この記事がみなさんの参考になれば幸いです

Discussion