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アプリが長らく求めてきた「静的サイトの速度 + 動的レンダリングの鮮度」の組み合わせです。

設定はたった1行、肝はCache-Controlヘッダー
Workers Cacheを有効にする方法は驚くほどシンプルです。wrangler.jsoncにcache.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回だけ実行されます。

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内で階層的に構成できます。他のプラットフォームでは見られないレベルの柔軟性です。

実務適用時の注意点とまとめ
注意点
- リクエスト課金: キャッシュヒットはCPU時間が課金されませんが、リクエスト数は標準料金が発生します。静的アセットリクエストもキャッシュ参照を通るため、課金が発生する可能性があります。
- マルチテナントキャッシュキーの設計:
ctx.propsを細分化しすぎるとキャッシュヒット率が低下します。ユーザーIDではなく「権限グループ」単位でキーを設計することも検討してください。 - Varyヘッダーの過剰使用:
Vary: *はキャッシュを完全に無効化します。必要なヘッダーのみを指定しましょう。
次のステップ学習方向
- Cloudflare Workers Cache公式ドキュメントで全機能とサンプルを確認してください。
- Astro、TanStack Startなどフレームワーク統合の実例を実際にハンズオンしてみてください。
stale-while-revalidateとCache-Tagを活用した無効化パターンを習得すれば、より高度なキャッシュ戦略を構築できます。