Workers Cache: もうWorkerは毎回レンダリングしなくていい

Cloudflare Workersが2017年に登場した当初は、「オリジンの前段に置くリバースプロキシ」が主な役割でした。しかし現在、Astro、Next.js、Remix、SvelteKitといった主要フレームワークがすべてCloudflareアダプターを提供するようになり、Workerは単なるリクエスト変換ツールからサーバーそのものへと進化しました。

問題は「Workerがオリジンである」場合に発生します。毎回のリクエストでコードが実行されるからです。Workersランタイムがどれだけ高速でも、ページロードごとにレンダリングコストが発生します。CPU時間が課金され、ユーザーはその分のレイテンシを甘受しなければなりません。

Workers Cacheはこの構造を完全に逆転させます。CloudflareのキャッシュがWorkerの前面に配置されます。

ユーザー → Cloudflare CDNキャッシュ → (Cache Hit: Worker実行なし) → レスポンス
ユーザー → Cloudflare CDNキャッシュ → (Cache Miss: Worker実行) → レスポンス保存 → レスポンス

Cache Hitが発生すると、Workerは一切実行されません。CPUコストはゼロで、レスポンスはCDNエッジから即座に返されます。これこそがSSRアプリが長らく求めてきた「静的サイトの速度 + 動的レンダリングの鮮度」の組み合わせです。

Cloudflare Workers Cache architecture diagram showing cache layers between user and origin Programming Illustration

設定はたった1行、肝はCache-Controlヘッダー

Workers Cacheを有効にする方法は驚くほどシンプルです。wrangler.jsonccache.enabled: trueを1行追加するだけです。

{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-01",
  "cache": {
    "enabled": true
  }
}

あとはWorkerのレスポンスに標準のCache-Controlヘッダーを設定するだけでキャッシュが動作します。別途キャッシュルールエンジンやページルールを設定する必要はありません。

// Workerコード: キャッシュのすべてはHTTPヘッダーで制御
export default {
  async fetch(request) {
    const html = await renderPage(request);
    return new Response(html, {
      headers: {
        "Content-Type": "text/html; charset=utf-8",
        // 5分間はフレッシュ、その後1時間はstale-while-revalidate
        "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
      },
    });
  },
};

stale-while-revalidate: 体感パフォーマンスの鍵

stale-while-revalidateディレクティブは、キャッシュが期限切れになった後でも即座に古い(stale)レスポンスを返し、バックグラウンドで新しいレスポンスを取得します。ユーザーは待たされることがありません。

  • Fresh window (max-age): キャッシュレスポンスを返却。Workerは実行されない。
  • Stale window (stale-while-revalidate): 古いレスポンスを返却。Workerはバックグラウンドでリフレッシュ。
  • Both expired: Workerを実行してレスポンスを生成。ユーザーはこの1回だけ待つ。

実務のヒント: 商品カタログであればmax-age=300, stale-while-revalidate=3600と設定することで、訪問者はほぼ常にキャッシュ速度を体感し、Workerは5分に1回だけ実行されます。

Developer configuring Workers Cache in wrangler.jsonc file on a laptop Technical Structure Concept

Varyヘッダー、マルチテナントキャッシュ、エントリーポイント間キャッシング

Vary: 1つのURL、複数の表現

ブラウザにはHTML、APIクライアントにはJSON、言語ごとに異なるコンテンツを提供する必要がある場合、Varyヘッダーが答えです。

export default {
  async fetch(request) {
    const accept = request.headers.get("Accept") ?? "";
    const wantsWebp = accept.includes("image/webp");
    const body = wantsWebp ? await fetchWebpImage() : await fetchJpegImage();
    return new Response(body, {
      headers: {
        "Content-Type": wantsWebp ? "image/webp" : "image/jpeg",
        "Cache-Control": "public, max-age=3600",
        "Vary": "Accept",  // Acceptヘッダーの値ごとに別々のキャッシュ
      },
    });
  },
};

ctx.props: マルチテナントセーフティ

ユーザーごとのデータをキャッシュする際、最大の懸念は「他のユーザーのデータが漏洩すること」です。Workers Cacheはctx.propsをキャッシュキーの一部として使用することで、この問題を解決します。

import { WorkerEntrypoint } from "cloudflare:workers";

interface Props { userId: string; }

export default class Backend extends WorkerEntrypoint<Env, Props> {
  async fetch(request: Request): Promise<Response> {
    // ctx.props.userIdがキャッシュキーの一部になります。
    // 同じURLでもuserIdが異なれば別のキャッシュエントリー。
    const { userId } = this.ctx.props;
    const data = await loadUserData(userId);
    return new Response(JSON.stringify(data), {
      headers: {
        "Content-Type": "application/json",
        "Cache-Control": "public, max-age=300",
      },
    });
  }
}

エントリーポイント間キャッシング: 真のコンポーザブルアーキテクチャ

最も革新的な部分は、同じWorker内の異なるエントリーポイントの間にキャッシュを挟み込める点です。認証は毎回実行する必要があるが、データ取得はキャッシュしたい場合:

{
  "cache": { "enabled": true },
  "exports": {
    "default": { "type": "worker", "cache": { "enabled": false } },
    "CachedBackend": { "type": "worker", "cache": { "enabled": true } }
  }
}
export default {
  async fetch(request, env, ctx) {
    // 認証: 常に実行
    const userId = await authenticate(request);
    if (!userId) return new Response("Unauthorized", { status: 401 });

    // キャッシュされたバックエンドを呼び出し
    const forwarded = new Request(request);
    forwarded.headers.delete("Authorization");
    return ctx.exports.CachedBackend.fetch(forwarded, {
      props: { userId },
    });
  },
} satisfies ExportedHandler;

このパターンを活用すれば、認証、URL正規化、トラッキングパラメータ除去、Durable Objectのキャッシュなどを1つのWorker内で階層的に構成できます。他のプラットフォームでは見られないレベルの柔軟性です。

Server-side rendered application performance optimization with Cloudflare Workers Cache Development Concept Image

実務適用時の注意点とまとめ

注意点

  1. リクエスト課金: キャッシュヒットはCPU時間が課金されませんが、リクエスト数は標準料金が発生します。静的アセットリクエストもキャッシュ参照を通るため、課金が発生する可能性があります。
  2. マルチテナントキャッシュキーの設計: ctx.propsを細分化しすぎるとキャッシュヒット率が低下します。ユーザーIDではなく「権限グループ」単位でキーを設計することも検討してください。
  3. Varyヘッダーの過剰使用: Vary: *はキャッシュを完全に無効化します。必要なヘッダーのみを指定しましょう。

次のステップ学習方向

  • Cloudflare Workers Cache公式ドキュメントで全機能とサンプルを確認してください。
  • Astro、TanStack Startなどフレームワーク統合の実例を実際にハンズオンしてみてください。
  • stale-while-revalidateCache-Tagを活用した無効化パターンを習得すれば、より高度なキャッシュ戦略を構築できます。

合わせて読みたい記事

本コンテンツは、信頼性の高い情報源をもとにAIツールを活用して作成され、編集者によるレビューを経て公開されています。専門家によるアドバイスの代替となるものではありません。