Workerが前段に独自のキャッシュを持てるようになりました
2026-07-06 — Dan Lapid, Connor Harwood — 17 min read
今日、Workers Cache を発表します。これは Worker の前段に置かれる階層型キャッシュで、Wrangler の設定の一行と、これまでと同じ Cache-Control ヘッダーで構成できます。Workers Cache を有効にすると、Worker に対するキャッシュ可能なすべてのリクエストはまず Cloudflare のキャッシュに到達します。最新のキャッシュがあれば Cloudflare がそれを直接返し、Worker は実行されず、CPU 時間は課金されません。ミス(cache miss)の場合は Worker が実行され、レスポンスがキャッシュ可能であれば Cloudflare が次回のリクエストのために保存します。次のリクエストは地球上どこからでもキャッシュから直接返されます。
設定は1つのブロックだけです:
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2026-05-01",
"cache": {
"enabled": true
}
}
その後は、HTTP が望んでいた通りにヘッダーでキャッシュを制御します:
return new Response(body, {
headers: {
"Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
"Cache-Tag": "products,product:123",
},
});
コンテンツが変わったときは、Worker 自身がそのキャッシュをパージします:
await ctx.cache.purge({ tags: ["product:123"] });
それが API のすべてです。ゾーンを設定する必要も、ルールエンジンを準備する必要も、別のキャッシュをプロビジョニングする必要も、別製品にログインする必要もありません。Worker のコードが設定面(configuration surface)であり、キャッシュは Worker が実行される場所(カスタムドメイン、workers.dev、サービスバインディングの裏、プレビュー、Workers for Platforms テナント)に追随します。1つの Worker、1つのキャッシュ、一度の設定。それが表面上の話です。
内部には多くの仕組みがあります:
- ネットワーク全体での階層型キャッシング
- stale-while-revalidate のフルサポート(スタールなレスポンスがユーザーを待たせない)
- Vary によるコンテンツネゴシエーション対応
- ctx.props によるマルチテナント安全なキャッシュキー
- タグやパスプレフィックスによるプログラム的なパージ
- そして我々が最大の解放(unlock)だと考える点:全ての Worker エントリポイントの前に配置できるキャッシュ。公開エントリポイントだけでなく、各エントリポイントごとにキャッシュの有無を制御できます。
最後の点により、アプリの構造にキャッシュを直接組み込めます。エントリポイントのチェーンにキャッシュ段を任意の場所で差し込めるようになり、両側のコードで設定できます。以下でその全てを説明します。
Workers Cache は本日からすべてのプランの全ての Worker で利用可能で、Wrangler で有効化できます。これは常に Workers に欲しかったキャッシング API です。なぜここまで時間がかかったのか、これによって何が可能になるのか、次に何が来るのかを説明します。
サーバーサイドレンダリングアプリが前段キャッシュを必要とする理由
2017 年に Workers を導入したとき、目的は Cloudflare のネットワーク上でコードを実行してオリジンへ向かうリクエストを変換することでした。Worker はキャッシュとオリジンの前に位置していました。これは当時想定していたユースケースに適したモデルでした。全てのリクエストにヘッダーを追加したり、URL を書き換えたり、A/B 分割を行ったり、オリジンに到達する前にトラフィックをフィルタリングしたいなら、Worker をキャッシュとオリジンの前に置くことで、何がキャッシュされるべきか完全に制御できました。顧客は素晴らしいものを構築しました。
しかし世界は変わりました。Worker はオリジンに付け加えるものではなく、オリジンそのものになりました。Astro、TanStack Start、Next.js、Remix、SvelteKit のようなフレームワークは Cloudflare アダプタを提供し、アプリを Worker としてビルドします。背後にオリジンはありません。Worker がサーバーです。
Worker がオリジンになると、従来のアーキテクチャにはキャッシュするものがありません。レスポンスが前回とバイト単位で同一であっても、すべてのリクエストでコードが走ります。Workers ランタイムは高速なのでこれでも動きます — 毎秒数千万リクエストを扱うこともあります — が「すべてのリクエストをレンダリングするのに十分速い」ことは、各ページロードごとにレイテンシと CPU コストが掛かるということです。サーバーサイドレンダリングされたアプリでは、ページロード=レンダリングです。
Workers Cache はこのアーキテクチャをひっくり返します。Cloudflare のキャッシュが Worker の前に置かれます:
- キャッシュヒット時は Worker はまったく実行されません。Cloudflare がキャッシュ済みレスポンスを返し、CPU 課金はゼロです。
- ミス時は Worker が一度実行され、キャッシュを埋めます。次のリクエストはどこからでもキャッシュから提供され、コードは呼ばれません。
これが Worker 上でのサーバーサイドレンダリングに欠けていたものです。従来は次の2つの不満の残る選択肢のどちらかを選ぶ必要がありました:
- ビルド時にすべてをプリレンダー(静的サイト生成)する。ページは高速だが、変更のたびにフルビルドと再デプロイが必要。数千ページのドキュメントサイトでも5〜10分、大規模な e コマースではそれ以上。
- すべてのリクエストで毎回レンダリングする。常に最新だが、各ページロードはレンダリングコストとレイテンシを負担する。
Workers Cache により第3の選択肢が得られます: 必要時にサーバーでレンダリングし、レンダリング済みレスポンスをキャッシュし、TTL で更新する。新しいページへの最初のリクエストはまだレンダリングしますが、その後はキャッシュが有効な限り静的なように振る舞います。キャッシュが失効したら次のリクエストが再レンダリングをトリガーしますが、stale-while-revalidate を使えばそれすら待たせません。ビルド時間なしに静的サイトの速さを得て、サーバーサイドレンダリングの鮮度をコストなしに保てます。Incremental Static Regeneration のようなフレームワーク固有の仕組みは不要です。HTTP キャッシングが設計どおりに働き、コードはオリジンとして機能します。
stale-while-revalidate が“瞬時”の感覚を生む
stale-while-revalidate ディレクティブは、キャッシュ済みレスポンスが期限切れのときに、Cloudflare がバックグラウンドでレスポンスを更新している間は古いコピーを即座に返してよいことを指示します。Cloudflare は今年初めに stale-while-revalidate のフルサポートを提供し、これが「Worker をキャッシュする」ことを「Worker のサイトが静的に感じられる」ものに変えます。
このディレクティブがないと、キャッシュエントリが失効した直後の最初のリクエストはページを再レンダリングするまで待たされ、ユーザーはそのレイテンシを体験します。ディレクティブがあると、失効直後の最初のリクエストは古いページをすぐに受け取り(Cf-Cache-Status: UPDATING ヘッダー付き)、Worker はバックグラウンドでキャッシュをリフレッシュします。更新を引き起こしたユーザーを含め、すべてのユーザーがキャッシュ速度のレスポンスを受け取ります。
実際の例:
export default {
async fetch(request) {
const html = await renderPage(request);
return new Response(html, {
headers: {
"Content-Type": "text/html; charset=utf-8",
"Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
},
});
},
}
理解のためのメンタルモデル:
- フレッシュウィンドウ(max-age): Cloudflare はキャッシュ済みレスポンスを返す。Worker は実行されない。
- ステールウィンドウ(stale-while-revalidate): Cloudflare はキャッシュ済みレスポンスを返す。Worker はバックグラウンドで実行され、キャッシュを更新する。どのユーザーも待たされない。
- どちらのウィンドウ外: Cloudflare は Worker を実行して新しいレスポンスを生成し、ユーザーはそのレンダリングを待つ。
ウィンドウはあなたが選びます。数分ごとに更新される商品カタログなら max-age=300, stale-while-revalidate=3600 の組み合わせで訪問者は基本的に待たず、Worker は十分な頻度で実行されて鮮度を保てます。ほとんど変わらないブログアーカイブなら max-age=86400, stale-while-revalidate=2592000 にしておけば、ページごとに毎日一回だけ Worker が実行されます。新しいページへの最初のリクエストだけがフルレンダリングのコストを払います。その後は訪問者にとって静的出力のように振る舞いますが、ページの生成方法は Worker が管理します。
1 つの URL、複数の表現: Vary が機能する
実際のアプリは同じバイト列をすべてのクライアントに返すことは稀です。ある製品ページはブラウザ向けに HTML を返し、API クライアント向けに JSON を返すかもしれません。同じ画像は WebP をサポートするクライアントには WebP を、そうでないクライアントには JPEG を返すかもしれません。同じホームページがユーザーに応じて英語、フランス語、日本語で返ることもあります。
キャッシュなしでこれを行うのは簡単です — Worker はリクエストヘッダーを読み正しいものを返します。キャッシュありでこれを行うと厄介になります。多くのキャッシュは二つの悪い選択肢しか与えません: 複数の表現がある URL では何もキャッシュしない、または一つの表現だけをキャッシュしてそれを全員に返す。
Workers Cache は標準の HTTP Vary ヘッダーをサポートします。これは正しい解です。Worker が Vary: Accept-Encoding(あるいは Accept、Accept-Language、その他のリクエストヘッダー)を含むレスポンスを返すと、Cloudflare はそれらのヘッダーの組み合わせごとに別々のキャッシュバリアントを保存し、受信したリクエストのヘッダー値と一致するバリアントだけを返します。
例:
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",
},
});
},
}
1 つの URL に対して 2 つのキャッシュバリアント。Accept: image/webp,/ を送るブラウザは WebP を受け取り、Accept: image/jpeg を送るブラウザは JPEG を受け取り、どちらもキャッシュから返されます。Worker はそれぞれの最初のリクエストで両方のバリアントを書き込み、それ以降は両方ともゼロ回実行されます。
これはコンテンツネゴシエーションに対するよく知られた HTTP 標準であり、Workers Cache は RFC 9110 と RFC 9111 が記述する方法どおりに実装します。Vary で使用できるヘッダーに対する許可リストはありません。必要なものを列挙すればよく、Cloudflare はそのままの値でバリアントをキー化します。ドキュメントでは、ゲートウェイ Worker でヘッダーを正規化してバリアントのファンアウトを抑える方法、パージが URL のすべてのバリアントを一緒に無効化する理由、そして(Vary: *)だけがキャッシュを完全に無効化するケースについて説明しています。
これはゾーンのキャッシュではなく、あなたの Worker のキャッシュです
先に進む前に、概念的な変化を名前で呼んでおく価値があります。Cloudflare は長年キャッシュを持ってきました。それはゾーンレベルで構成されます: Cache Rules、Page Rules、cached-file-extensions リスト、Cache Reserve、Tiered Cache トポロジー、カスタムキャッシュキーなど。これらはすべてゾーンごとに設定され、従来は Worker はそのゾーンの設定に合わせるか回避策を取る必要がありました。
Workers Cache は異なります。これはあなたの Worker のキャッシュです — ゾーンではなく Worker に属します。これには重要な帰結があります:
- 管理すべきゾーン設定がありません。Cache Rules、cache level 設定、file-extensions リスト、Page Rules — これらは Workers Cache には適用されません。Worker の Cache-Control ヘッダーが設定です。
- キャッシュはホスト名ではなく Worker に追随します。ある Worker が api.example.com、api.example.net にバインドされサービスバインディング経由で呼ばれている場合、それらは同じキャッシュを共有します。/users/42 へのリクエストはどの経路から入ったとしても同じキャッシュエントリに当たります。
- キャッシュは workers.dev でも動作します。プレビュー URL でも動作します(各プレビューは独自のキャッシュを持つので、変更のテストが本番を汚染しません)。Workers for Platforms でも機能します(各ユーザーワーカーはディスパッチャや他のテナントから隔離された独自のキャッシュを持ちます)。これらはかつてキャッシングにとって二級市民だったものですが、もはやそうではありません。
パージは Worker にスコープされます。