プロダクト発表 · 小互解読

Cloudflare が Workers Cache を発表——キャッシュを各 Worker の手前に前置し、ヒット時は CPU 課金なし

Wrangler の設定を一行加えるだけで標準 HTTP キャッシュヘッダーが使え、キャッシュは Worker 内部の任意のエントリーポイント間にも組み込める。
出典 · Cloudflare Blog 読了 · 7 分 Cloudflare Workers · エッジキャッシュ · Serverless
30 秒でわかる要点
  • Cloudflare は本日、Workers Cache を発表。リージョン階層キャッシュを各 Worker の手前に直結し、Wrangler の設定を一行加えるだけで有効化できる。
  • キャッシュヒット時は Worker が一切実行されず、CPU 課金も発生しない。未ヒットの時だけコードが実行され、上下 2 層のキャッシュに書き戻す。
  • キャッシュはもうドメイン(zone)に紐づかず、Worker に付随する——workers.dev、プレビュー環境、Workers for Platforms でもすべて使え、パージも自分自身にしか影響しない。
  • 最大のブレークスルーは、同一 Worker 内部の任意のエントリーポイント間にキャッシュを差し込めること。ctx.props と組み合わせれば、ログイン済みユーザー向け API も安全にキャッシュを共有できる。
  • 本日よりすべてのプランの Worker で開放。単独プロダクトではなく、追加料金項目もない。
スタンス表明:本稿は Cloudflare 公式発表ブログを基にした編訳記事。文中の「ヒット時は CPU 課金ゼロ」「約 50ms で世界の約 95% のネットユーザーをカバー」「他のプラットフォームや CDN では見たことがない」といった記述は、すべてベンダー自身の言い分である。
1 何なのか

Worker 自身にもキャッシュ層を持てるようになった

Cloudflare は本日、Workers Cache を発表した。Worker の手前に直結されたキャッシュ層で、有効化には設定を一行加えるだけ、制御にはあなたがすでに使い慣れている標準 HTTP キャッシュヘッダーを使う。

キャッシュがヒットすると、あなたの Worker はまったく実行されず、そのリクエストは CPU 課金も発生しない。未ヒットの時だけコードが一度実行され、実行結果はそのままキャッシュに保存される。次のリクエストは、地球上どこから来ても、直接キャッシュから読める。

なぜ注目に値するか:Cloudflare 公式の言い分によれば、単一のデプロイ単位の内部にキャッシュを組み込み、エントリーポイントごとに個別にオン/オフできるプラットフォームは他に見たことがなく、「ログイン済みユーザーの API 間で安全にキャッシュを共有する」ことを組み込み機能にした CDN も他に見たことがないという。

キャッシュを有効化する設定は、これだけ
{
  "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 全体はこれだけしかない。設定すべき zone もなく、構築すべきルールエンジンもなく、別途有効化すべきキャッシュ製品もなく、ログインすべき第二のプロダクトもない。Worker のコードそのものが設定面であり、キャッシュは Worker が動く場所どこへでも付いてくる——カスタムドメイン、workers.dev、service binding の背後、プレビュー環境、Workers for Platforms のテナント内、すべて一つの Worker、一つのキャッシュ、一度の設定で済む。

0
キャッシュヒット時に Worker が消費・課金される CPU 時間
~50ms
Cloudflare ネットワークが世界の約 95% のネットユーザーをカバーするのに必要な最大遅延半径
512MB
現時点で全プラン共通のキャッシュ可能なレスポンスサイズ上限(今後プランごとに区分予定)
2 背景

Worker はもともとキャッシュの手前に立っていたが、今は自分自身がオリジンになった

2017 年に Cloudflare が Workers を初めてリリースした時、売りはコードをネットワークエッジで動かし、リクエストがオリジンへ向かう途中でそれを書き換えられることだった。当時 Worker はキャッシュとオリジンの手前に立っていた。

オリジンとは、あなたのサイトのコンテンツを実際に保持し、ページを生成する責任を持つサーバーのことだ。Worker がその手前に立っている時、リクエストヘッダーを追加したり、URL を書き換えたり、A/B 分岐をしたり、トラフィックがオリジンに到達する前にフィルタリングしたりできた。当時のそうしたシナリオでは、この位置は正しかった——何をキャッシュし、何をキャッシュしないかを完全にコントロールできた。

その後、状況が変わった。Astro、TanStack Start、Next.js、Remix、SvelteKit といったフレームワークはいずれも Cloudflare アダプターを提供し、アプリケーション全体を一つの Worker にまとめてしまう。これらの背後にはオリジンが存在せず、Worker 自体がそのサーバーになる。

2017 · 旧アーキテクチャ
リクエスト Worker キャッシュ オリジン
Worker が最前面でトラフィックを迎え、キャッシュはその背後のオリジンの手前で待ち構える。
現在 · Workers Cache
リクエスト キャッシュ Worker(オリジンそのもの)
キャッシュが最前面に立ち、ヒットすればそのまま返す。Worker は一度も動かない。

Worker がオリジンそのものになると、旧アーキテクチャでキャッシュがその背後を守っていた構図では、もうキャッシュできるものがなくなる。毎回のリクエストでコードを再実行する羽目になる——たとえ返す内容が 1 秒前とまったく同じでも。Workers のランタイムは十分速く、リクエストごとにレンダリングし直しても平気で、1 秒間に数千万リクエストをさばいても息切れしない。だが「毎回レンダリングできるほど速い」ことには、依然として 2 つのコストがつきまとう——ページ読み込みごとの遅延と、実行のたびの CPU 時間だ。サーバーサイドレンダリングのアプリケーションにとっては、ページ読み込みごとが定義上そのままレンダリングの実行にあたる。

これまでは、どちらも痛みを伴う 2 つの選択肢からしか選べなかった。

ビルド時の全量プリレンダリング
ページ読み込みは速いが、変更するたびにサイト全体を再ビルド・再デプロイする必要がある。数千ページのドキュメントサイトでも 5〜10 分かかり、大規模な EC サイトはさらに悪化する。何かに触れるたびに全部やり直しになる。
リクエストごとの都度レンダリング
コンテンツは常に最新だが、ページ読み込みのたびにレンダリングコストを払い、訪問者一人ひとりが遅延を払う。

Workers Cache は 3 つ目の道を用意した。オンデマンドでサーバーサイドレンダリングし、レンダリング結果をキャッシュし、選んだ有効期限に沿って更新する。新しいページの最初のリクエストは普通にレンダリングされ、それ以降キャッシュが切れるまでは、毎回のリクエストが静的ページを見るのと同じように配信される。静的サイトの速度が手に入り、ビルド時間は省け、サーバーサイドレンダリングの新鮮さも手に入り、継続的なコストは省ける。

3 新アーキテクチャ

キャッシュが逆転し、Worker の手前に立つ

Workers Cache はデフォルトでリージョン階層の 2 層キャッシュになっており、このトポロジーは自動的に有効になる——何も設定する必要はない。リクエストが来た時、どの層をまず確認し、いつ初めてあなたのコードが本当に呼び出されるかは、次のように進む。

リクエスト到着 下層キャッシュ ユーザーに最も近いデータセンター ヒット 直接返却 未ヒット 上層キャッシュ 全ネットワーク集約、数は少ない ヒット 返却して下層に書き戻す 両層とも無し Worker を一度実行 このリクエストだけが本当に都度レンダリングされる 結果を上層・下層の両方のキャッシュに書き込む 世界で最初のリクエストが上層キャッシュを埋める 以後どのデータセンターからのリクエストも直接ヒットできる、たとえ初めて見るものでも
2 層のリージョンキャッシュ:下層は近さ重視、上層は集約重視。最初のリクエストで上層を満たせば、全ネットワークで再利用される。
たとえるなら

チェーン展開するコンビニの仕入れのようなものだ。まず自店の棚を見て(下層キャッシュ)、無ければ地域倉庫に発注し(上層キャッシュ)、倉庫にも無ければメーカーに作ってもらう(Worker を実行)。メーカーがこのバッチを作れば、棚も倉庫も補充され、次に誰が来てもメーカーを煩わせる必要はない。

このトポロジーは、今日 zone 向けに提供されている Tiered Cache と同じものだ。違いは、あなたがそれを設定する必要がないこと——「私の Worker に階層キャッシュを有効化する」といったスイッチは存在しない。Worker がキャッシュを有効にしてさえいれば、階層構造はおまけで付いてくる。もしあなたの Worker が Smart Placement を使っているなら、キャッシュもそれときれいに重なり合う。まず 2 層のキャッシュを確認し、両方とも未ヒットの時に初めて、Smart Placement が実行をデータに近い場所へスケジューリングする。

4 重要な仕組み

キャッシュが期限切れになった瞬間、誰も待たされない

stale-while-revalidate は直訳すると「古いままで再検証する」。これは Cloudflare にこう伝えている——キャッシュが期限切れになったら、まず古いものをそのままユーザーに即座に返してよい、その裏で密かに Worker をもう一度実行して新しいものに差し替える、と。まさにこの一文が、「あなたの Worker をキャッシュしてあげる」を「あなたの Worker のサイトが静的サイトのように感じられる」に変えている。

これが無ければ、キャッシュ期限切れ後の最初のリクエストは Worker がゼロからレンダリングし終わるのを待つ必要があり、その遅延はユーザーにも体感できるレベルだ。これがあれば、期限切れ後の最初のリクエストは即座に古いページを受け取り(Cf-Cache-Status: UPDATING ヘッダー付き)、Worker は裏側でキャッシュを埋め直す。この更新をトリガーした本人を含め、すべてのユーザーがキャッシュ並みの速さのレスポンスを受け取る。

キャッシュ生成直後時間は右へ →だんだん古くなる
新鮮期
max-age 以内
Cloudflare がキャッシュを直接返す。
Worker は動かない
陳腐期
stale-while-revalidate 以内
まず古いコンテンツを即座にユーザーへ返し、Worker は裏で更新を実行する。誰も待たなくていい。
Worker がバックグラウンドで実行
二重期限切れ
両方の窓を過ぎた
Cloudflare は Worker をその場で一度実行して新しいレスポンスを生成する。このリクエストだけがこのレンダリングを待つことになる。
Worker がその場で実行

この窓の長さはあなたが決める。コンテンツがどれくらいの頻度で変わるかに合わせて設定すればいい。

数分おきに変わる商品カタログ
max-age=300
stale-while-revalidate=3600
訪問者はほぼ待たされず、Worker も十分な頻度で実行されるので、コンテンツは新鮮に保たれる。
ほとんど変わらないブログアーカイブ
max-age=86400
stale-while-revalidate=2592000
各ページの Worker 実行は 1 日に 1 回だけになる。

まったく新しいページの最初のリクエストだけが、完全なレンダリングコストを一度払う。それ以降、訪問者から見ればこのページの挙動は静的な出力と変わらないが、ページの生成方法はあくまであなたの Worker が決めている。

5 概念の転換

このキャッシュは Worker に付随し、ドメインには付随しない

Cloudflare にはもともとキャッシュがあったが、それは zone(ドメインの設定単位)ごとに設定されていた。Cache Rules、Page Rules、キャッシュ対象ファイル拡張子リスト、Cache Reserve、階層キャッシュのトポロジー、カスタムキャッシュキー——すべて zone 上に設定されていた。これまで一つの Worker は、所属する zone の設定に合わせるか、それを回避する方法を考えるかしかなかった。

Workers Cache は所属先を変えた。これはあなたのこの Worker のキャッシュであり、Worker に属し、特定の zone には属さない。これにより実利的なメリットがいくつか生まれる。

  • 管理すべき zone 設定がない。Cache Rules、キャッシュレベル設定、ファイル拡張子リスト、Page Rules は、いずれも Workers Cache には適用されない。Worker の Cache-Control ヘッダーがすべての設定になる。
  • キャッシュは Worker に付随し、ホスト名には付随しない。一つの Worker が api.example.com と api.example.net の両方にバインドされ、さらに service binding 経由でも呼び出されるとして、この 3 つの経路は同じキャッシュを共有する。/users/42 へのリクエストは、どの経路から来ても、同じキャッシュエントリーにヒットする。
  • workers.dev でも使える。プレビュー環境はそれぞれ独立したキャッシュを持ち、ある変更のテストが本番を汚染することはない。Workers for Platforms でも各テナントの Worker がそれぞれ独立してキャッシュを持ち、互いに干渉しない。これらの場所はこれまでキャッシュにおいて二流市民扱いだったが、もうそうではない。
  • キャッシュのパージは自分のこのエントリーポイントにしか作用しない。purge を呼んでクリアしても、あなたのこの Worker のエントリーポイントのキャッシュだけがクリアされ、zone 内の他のコンテンツに誤爆することも、ある Worker のデプロイが別の Worker のデータを消してしまうこともない。

設定したいキャッシュの挙動は、すべてコードの中に書く。どのパスに長い有効期限を与えるかは、パスごとに分岐して異なる max-age を設定すればいい。どのリクエストをキャッシュから外すかは、Cache-Control: private を返せばいい。キャッシュキーの計算方法は、ctx.props に何を入れるか、ゲートウェイ Worker で先に URL を正規化するかをコントロールすればいい。すでに書き終えたあなたの Worker が、そのまま設定面になる。

6 最大のブレークスルー

キャッシュを Worker 内部の任意の継ぎ目に差し込める

Cloudflare が考える最大のブレークスルー

Workers Cache はあらゆる Worker のエントリーポイントの手前に立つ——デフォルトエクスポートのエントリーポイント、各具体名のエントリーポイント、そして同一 Worker 内部である一つのエントリーポイントが ctx.exports 経由で別のエントリーポイントを呼び出すその呼び出し自体も含む。この最後の一文が、あなたが組み立てられるものの姿を変える。

一つの Worker プログラム内部は、いくつかの独立した「サブエントリーポイント」(entrypoint)に分割でき、コードを書く際に直接互いを呼び出せる——実際にネットワークリクエストを一度外に送ってまた戻ってくる必要はない。あるエントリーポイントが ctx.exports 経由で別のエントリーポイントを呼び出す時、キャッシュはそれをブラウザからのリクエストと同じように評価する。

たとえるなら

ある会社で、受付(ゲートウェイのエントリーポイント)が来訪者の対応を終えたら、その要望を直接社内の対応部署(別のエントリーポイント)に回す——来訪者自身が道路の向かいのビルまでわざわざ足を運ぶ必要はない。キャッシュはちょうど受付と部署の間に挟まっている。部署の前回の回答がまだ残っていれば、受付はそれをそのまま来訪者に渡し、部署は今回ドアを開ける必要すらない。

ヒットすればキャッシュをそのまま返し、呼び出された側のエントリーポイントは一度も実行されない。未ヒットの時だけ一度実行され、自分のエントリーポイント・パス・クエリ文字列・ctx.props に基づいて一つのキャッシュを保存する。呼び出す側は毎回実行されるが、呼び出される側に投げた仕事は独立してキャッシュされる。どちらをキャッシュするかはエントリーポイントごとに決める——wrangler の exports で各エントリーポイントごとに個別にオン/オフできる。ゲートウェイやルーティングのように認証・正規化・振り分けを担うエントリーポイントは、キャッシュをオフにして毎回実行させるべきで、その出力自体が誤ってキャッシュとして返されることも永遠にない。

一つの Worker · 一つのソースファイル · 一度のデプロイ ゲートウェイ default · 認証 · ルーティング キャッシュ:オフ キャッシュスイッチ ヒット時は直接返却 内部計算エントリーポイント 重い処理 · ヒット時は実行なし キャッシュ:オン キャッシュはコードに書き込まれた一層であり、外付けの製品ではない
看板図:キャッシュは呼び出しチェーンの途中に埋め込まれたスイッチのようなもので、エントリーポイントごとに点灯・消灯できる。

キャッシュの設定方法は、すべて普通の Worker コードとして表現される——どのエントリーポイントを呼ぶか、何のリクエストを転送するか、どんな ctx.props を渡すか、どんな Cache-Control を設定するか。以下の Worker は、他のプラットフォームでは一緒にやりにくい 3 つのことを一気にやっている。すべてのリクエストで認証を行い、コストの高いバックエンドをマルチテナントで安全なキャッシュキーの背後にキャッシュし、データが変わったらそのキャッシュをクリアする。キャッシュはエントリーポイントごとに設定され、ゲートウェイは毎回実行される必要がある(認証のためでもあり、キャッシュされてしまうとゲートウェイがこの認証をスキップしてしまうためでもある)。そのためデフォルトのエントリーポイントはキャッシュをオフにし、内部エントリーポイントだけオンにする。

wrangler でエントリーポイントごとにオン/オフ:ゲートウェイはオフ、内部計算エントリーポイントはオン
{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-01",
  "cache": { "enabled": true },
  "exports": {
    // ゲートウェイは毎リクエスト実行する必要があるので、キャッシュしない
    "default": { "type": "worker", "cache": { "enabled": false } },
    // コストの高い内部エントリーポイントをキャッシュする
    "CachedBackend": { "type": "worker", "cache": { "enabled": true } }
  }
}
この Worker の完全なコードを見る(ゲートウェイ + キャッシュバックエンド + タグ指定による無効化)
src/index.ts · 一つのソースファイルの中の 2 つの実行段階、キャッシュはその間に挟まる
import { WorkerEntrypoint } from "cloudflare:workers";

interface Env { API_TOKEN: string; }
interface Props { userId: string; }

// 内部エントリーポイント:コストの高い処理。キャッシュがその手前に立ち、ヒット時はこのコードはまったく実行されない。
export class CachedBackend extends WorkerEntrypoint<Env, Props> {
  async fetch(request: Request): Promise<Response> {
    // ctx.props.userId はキャッシュキーの一部なので、ユーザーごとに別々にキャッシュされる。
    const { userId } = this.ctx.props;
    const data = await loadExpensiveData(userId);

    return new Response(JSON.stringify(data), {
      headers: {
        "Content-Type": "application/json",
        "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
        "Cache-Tag": `user:${userId}`,
      },
    });
  }

  // 特定ユーザーのキャッシュをクリアする。purge の作用範囲はそれを呼び出した
  // エントリーポイントに限られるので、このキャッシュを所有する CachedBackend の中で実行する必要がある。
  async invalidate(userId: string): Promise<void> {
    await this.ctx.cache.purge({ tags: [`user:${userId}`] });
  }
}

// 外側のエントリーポイント:毎リクエスト実行され、認証とルーティングを担当する。
// wrangler でキャッシュをオフにしているので常に実行され、認証がキャッシュヒットでスキップされることはない。
export default {
  async fetch(request, env, ctx): Promise<Response> {
    const userId = await authenticate(request, env);
    if (!userId) return new Response("Unauthorized", { status: 401 });

    // 書き込み操作の時、キャッシュを所有するエントリーポイントからこのユーザーのキャッシュをクリアする。
    if (request.method === "POST") {
      await handleWrite(request, userId);
      await ctx.exports.CachedBackend.invalidate(userId);
      return new Response("OK");
    }

    // 読み取り操作:まず Authorization を剥がす(そうしないと Cloudflare が
    // 自動的にキャッシュをバイパスしてしまい、何もキャッシュできなくなる)。
    // 認証済みのユーザー身元を ctx.props に入れてキャッシュバックエンドへ転送する。
    const forwarded = new Request(request);
    forwarded.headers.delete("Authorization");

    return ctx.exports.CachedBackend.fetch(forwarded, {
      props: { userId },
    });
  },
} satisfies ExportedHandler<Env>;

全体は一つの Worker、一つのソースファイル、一度のデプロイだ。しかし内部には 2 つの実行段階があり、短い exports の記述だけでゲートウェイのキャッシュをオフに、バックエンドのキャッシュをオンにし、キャッシュはその間に挟まって、ユーザーごとにキーが分かれ、書き込みパスがタグ指定でクリアし、バックグラウンド更新時には古いコンテンツを返す。このキャッシュ段階は外付けされたものではなく、プログラムの一層であり、コードの中に書かれている。

同じ形は他の場所にも当てはめられる。Durable Object を一つのエントリーポイントの背後に包み、読み取りヒット時はそれに触れず、書き込みは直接 Durable Object に入ってタグ指定でキャッシュをクリアする。ゲートウェイでトラッキングパラメータ(?utm_source=…)を剥がしてから転送し、キャッシュにはきれいな URL だけを見せることで、さまざまなパラメータ付きリンクを同じキャッシュエントリーに収束させる。これらのエントリーポイントは何層にも重ねられ、各層の間のキャッシュ段階は、あなたが設定するものではなく、あなたがどこに置くかを決めるだけのものになる。

7 マルチテナント安全性

ログイン済みユーザーの API も、安全にキャッシュを共有できる

もしあなたがキャッシュしたいのがユーザーごとに異なる内容を返す API——たとえばログイン済みの各ユーザーにそれぞれのデータを返す API だとしたら、あるユーザーが別のユーザーのキャッシュを絶対に見られないようにする必要がある。従来のやり方は「認証済みリクエストは一律キャッシュしない」で、Cloudflare は Authorization ヘッダー付きのリクエストに対してデフォルトでそうバイパスしている。しかし「何もキャッシュしない」は、パフォーマンス上のメリットを丸ごと捨てることに等しい。

Workers Cache の解決策は、呼び出し側の ctx.props もキャッシュキーに含めることだ。キャッシュする各リクエストに「これは誰のものか」という見えないタグを貼り付け、同じ URL でも、ユーザーごとにそれぞれ独立したキャッシュコピーを保存する。A のデータが誤って B にキャッシュとして返されることは絶対にない。

ユーザー A → /me ユーザー B → /me ゲートウェイ認証 Authorization を剥がす userId を ctx.props に入れる キャッシュエントリー · userId=A キャッシュエントリー · userId=B 同じ /me でも、それぞれ別々に保存され、混ざらない
ctx.props が「これは誰か」をキャッシュキーに書き込み、マルチテナント分離はキャッシュキー自身が処理する。
呼び出される側のバックエンド:ctx.props.userId がキャッシュキーに入り、ユーザーごとに別々に保存される
export default class Backend extends WorkerEntrypoint<Env, Props> {
  async fetch(request: Request): Promise<Response> {
    // ctx.props.userId はキャッシュキーの一部。ユーザー A と B が同じ URL に
    // リクエストしても、それぞれ独立したキャッシュエントリーになる。
    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 でリクエストを認証し、Authorization ヘッダーを剥がし、認証済みのユーザー ID を ctx.props に書き込んでから、キャッシュバックエンドの Worker を呼び出す。ゲートウェイは毎リクエスト実行される(認証のために必ず実行される必要がある)が、コストの高いバックエンドは、そのユーザーがまだキャッシュエントリーを持っていない時だけ実行される。もともと「キャッシュできない」認証 API が、「ユーザーごとにキャッシュされ、しかも完全な安全分離付き」に変わる——分離はキャッシュキーが代わりにやってくれる。Cloudflare によれば、他の CDN は「正しさ」と「ヒット率」のどちらかを選ばせるが、それを組み込み機能にした第二の会社は見たことがないという。

ついでに「ユーザーに近い + データに近い」の 2 段構成が組み上がる

Web パフォーマンスには長年の矛盾がある——コードをユーザーに近い場所で動かしたい(ユーザーからサーバーまでの往復がクリティカルパス上にある)一方、コードをデータに近い場所でも動かしたい(クエリのたびに一往復発生する)。どちらかを選べば、もう一方は遅くなる。Cloudflare のネットワークはそれ自体で約 50ms 以内に世界の約 95% のネットユーザーをカバーできる。Smart Placement と組み合わせれば、コードもデータに寄り添って動かせる。足りなかったパズルのピースが、まさにこのキャッシュ層だ。

ユーザー Worker A · ユーザーの近く 認証 · ルーティング · 外殻レンダリング キャッシュB のキャッシュ Worker B · データの近く DB クエリ · レンダリング · 重い処理 ヒット時:ユーザー → A → B のキャッシュがヒット → 返却、B へは一切飛ばない
キャッシュを継ぎ目として使う:人気のページは A で直接ヒットして返却され、データへのジャンプは未ヒットの時だけ発生する。

特別なアーキテクチャ設計をしなくても、これは手に入る。アプリケーションを 2 つの Worker に分けて書き、service binding で一方をもう一方に向け、Worker B の wrangler でキャッシュをオンにするだけでいい。

8 コンテンツネゴシエーション · モニタリング · 課金

コンテンツネゴシエーション、モニタリングダッシュボード、課金はどう計算されるか

実際のアプリケーションが、どのクライアントにもまったく同じバイト列を返すことは滅多にない。同じ商品ページでも、ブラウザは HTML を求め、API クライアントは JSON を求める。同じ画像でも、対応クライアントは WebP を求め、非対応クライアントは JPEG を求める。同じトップページでも、ユーザーごとに中国語・フランス語・日本語のいずれかを返すかもしれない。

一つの URL に複数バージョンを保存:Vary ヘッダー

キャッシュがなければこれは対処しやすい——Worker がリクエストヘッダーを読んで対応するものを返すだけでいい。難しいのはキャッシュを組み合わせた時だ。多くのキャッシュは 2 つの残念な選択肢しか用意していない——複数バージョンのある URL はいっそキャッシュしないか、一つのバージョンだけを全員に配信するかだ。Workers Cache は標準の Vary ヘッダーをサポートしている。Vary: Accept を返せば、Cloudflare は Accept の値ごとにそれぞれ一つずつ保存し、マッチしたリクエストにだけそのバージョンを返す。一つの URL に 2 つのキャッシュバリアント、Worker はそれぞれのバリアントに最初の一回だけ書き込み、それ以降は誰に対してもゼロ回の実行になる。Vary に指定できるヘッダーにホワイトリストはなく、あなたが列挙したものをそのまま値ごとにバリアント分けしてくれる。

キャッシュと Worker が同じダッシュボードに載る

Workers の Observability ダッシュボードは、今では呼び出しごとにキャッシュ情報を表示する。Worker ごとにヒット率の時系列推移や、ヒット(HIT)、未ヒット(MISS)、更新中(UPDATING)、バイパス(BYPASS)それぞれの内訳を確認できる。ヒット率が低い時は、ここが原因を調べる場所になる——BYPASS が多すぎるなら、何かが cookie を設定している可能性がある。MISS が多すぎるなら、キャッシュキーが思っているより細かく分かれている可能性がある。UPDATING が多すぎるなら、max-age がトラフィックの間隔より短い可能性がある。これらは Worker のログ、例外、CPU 時間、リクエスト数と同じダッシュボード上にあり、あちこち切り替える必要はない。

課金:ヒット時は CPU 課金なしだが、標準リクエスト費用が発生する 2 つの項目がある

キャッシュヒット時は Worker が実行されず、CPU 時間も課金されないが、それでも 1 回のリクエストとしてカウントされ、標準の Workers リクエスト費率で課金される——他のどの呼び出しとも変わらない。未ヒットとバイパスは通常通り計算される——リクエスト費用に CPU 時間が加算される。

シナリオリクエスト費用CPU 時間費用
キャッシュヒット HIT(Worker は実行されない)標準費率課金なし
キャッシュ未ヒット MISS(Worker が実行される)標準費率課金あり
キャッシュバイパス BYPASS(Worker が実行される)標準費率課金あり
静的アセットリクエスト標準費率課金なし
Worker 間呼び出し標準費率実行された分だけ課金

Workers Cache 専用の課金項目はなく、GB 単位のキャッシュストレージ費用もない。階層キャッシュ、キャッシュパージ、stale-while-revalidate、上記の分析機能——すべて含まれている。本来なら Worker を実行するはずだったリクエストがヒットとして配信された場合、標準のリクエスト費用は依然として発生するが、CPU 時間分は発生しないので、この 1 回は Worker で都度レンダリングするより安く済む。1 点注意すべきことがある——キャッシュを有効にすると、もともと無料だった静的アセットリクエストや、service binding や ctx.exports 経由の Worker 間呼び出しが、標準のリクエスト費率で課金されるようになる。なぜなら、今や毎回まず手前のキャッシュを確認する必要があるからだ。

9 導入方法 · ロードマップ

今日から有効化でき、公式もまだ改善を続けている

Workers Cache は本日よりすべてのプランのあらゆる Worker で開放され、Wrangler で有効化する。始め方はたった 3 ステップ——wrangler.jsonc に "cache": { "enabled": true } を追加し、再デプロイし、レスポンスに Cache-Control ヘッダーを設定し始めるだけだ。

公式はこれから対応する予定の項目もいくつか挙げている。

  • Smart Placement とキャッシュのより良い連携。現状、上層キャッシュと Smart Placement のターゲットは別々に選ばれており、完全に未ヒットの時、リクエストは Cloudflare のデータセンター間を 2 往復する可能性がある——1 往復は上層キャッシュの確認に、もう 1 往復はデータに近い Worker の実行に。両者はこの 2 つの選択を調整し、1 回の未ヒットで遠距離の往復が 1 回だけで済むようにする作業を進めている。
  • サイズ上限の緩和。リリース時点ではすべてのレスポンスが無料プランの 512MB キャッシュ可能上限を、アカウントを問わず一律で採用している。これは一時的な措置で、段階的なロールアウトを経てプランごとに区分される予定だ。
  • より多くのフレームワーク対応。Astro はすでに Workers Cache アダプターを組み込み済みで、サーバーサイドレンダリングのページは自動的に「一度レンダリング・キャッシュ・バックグラウンド更新」のフローに乗る。TanStack Start、Next.js(Vinext 経由)なども追随中だ。
  • ctx.cache.invalidate() の追加。purge はマッチしたレスポンスをキャッシュから直接削除するのに対し、invalidate はそれらを「期限切れ」状態にする。そうすれば次のリクエストでも stale-while-revalidate によってまず速い古いコンテンツを受け取れ、Worker はバックグラウンドで更新する。
Worker はかつてキャッシュの手前で動いていたが、今ではキャッシュの背後でも動ける。必要な側を使えばいいし、service binding を使えば両方を同時に使うこともできる。 — Cloudflare Blog『Your Worker can now have its own cache in front of it』
出典:The Cloudflare Blog、2026 年 7 月 6 日発表、原文『Your Worker can now have its own cache in front of it』(blog.cloudflare.com/workers-cache/)。本稿はベンダー発表内容の日本語解説であり、コード例とデータはいずれも原文からの引用、文中のアーキテクチャ図・フローチャート・タイムラインはいずれも解読サイトが原文の説明に基づいて作成したもの。ヒット時 CPU 課金ゼロ、約 50ms で 95% のネットユーザーをカバー、「他のプラットフォームや CDN では見たことがない」といった記述は Cloudflare 公式の見解である。