Workers Cache を使うと、Worker のコードを実行せずに、キャッシュ済みの HTTP レスポンスを Cloudflare が返せます。着信リクエストがキャッシュ済みレスポンスに一致すると、Cloudflare はエッジキャッシュから直接レスポンスを返します。レイテンシと Workers の CPU 使用量を抑えられます。
キャッシュは、Worker への任意の fetch() 呼び出しで機能します。eyeball リクエスト(ブラウザーや API クライアントからのリクエスト)、サービスバインディング 経由のリクエスト、ctx.exports によるエントリポイント間のループバック fetch() です。レスポンスに付ける標準の HTTP Cache-Control ディレクティブで、キャッシュを制御します。
Workers Cache は その Worker 専用のキャッシュ です。所有も運用も Worker 側で、その Worker に対して非公開です。
Worker はゾーンに属さないエンティティです。任意の数の ゾーン にバインドでき、workers.dev で動かすことも、ゾーンを一切経由せずサービスバインディングだけで呼び出すこともできます。キャッシュはゾーンではなく Worker に付くため、次のことが成り立ちます。
- ゾーンのキャッシュ設定は Workers Caching には適用されません。 Cache Rules、Cache Response Rules、Page Rules、キャッシュレベルの設定、ゾーンの デフォルトでキャッシュされる拡張子 の一覧、そのほかすべてのゾーン単位のキャッシュ制御は、Worker のキャッシュに影響しません。
- 制御は Worker 側にあります。 レスポンスに
Cache-Controlヘッダーを付ければ、Cloudflare は RFC 9111 ↗ に従ってそれを守ります。設定面はこれだけです。 - キャッシュは、Worker の呼び出し方を問わず共有されます。
api.example.comとapi.example.netにバインドされ、サービスバインディングでも呼び出される Worker は、3 とおりすべてに同じキャッシュ済みレスポンスを返します。キャッシュキーはホスト名ではなく、リクエストパス、エントリポイント、ctx.props、(デフォルトでは)Worker のバージョンです。詳細は キャッシュキー を参照してください。
Worker は すでにいくらでもカスタマイズできます。レスポンス本文の変更、ヘッダーの書き換え、任意のリクエスト属性での分岐、サービスバインディングや ctx.exports によるほかの Worker の呼び出し、システム全体にわたるロジックの組み合わせができます。
Workers Caching は、その仕組みに寄せています。キャッシュ挙動のための別の設定層を足すのではなく、Worker が意図を直接表せます。返す Cache-Control ヘッダー、受け取る ctx.props、発行するプログラムからのパージです。キャッシュについて設定したいことは、コードで設定できます。
- 特定のパスだけ TTL を長くしたい場合は、Worker 内でパスを分岐し、別の
max-ageを設定します。 - キャッシュ前にトラッキング用のクエリパラメーターを除きたい場合は、ディスパッチ前にゲートウェイ Worker で URL や
ctx.propsを書き換えます。 - テナントごとにキャッシュを分けたい場合は、テナント識別子を
ctx.propsに入れます。キャッシュキーに含まれます。 - 認証済みユーザーではキャッシュを避けたい場合は、
Cache-Control: privateを返すか、Set-CookieとAuthorizationによる 自動バイパス に任せます。
すでに書いた Worker が、そのまま設定の仕組みです。Workers Caching はその手前で動き、Worker が返すヘッダーを守ります。
キャッシュは、次のような Worker に向いています。
- 結果をリクエスト間で再利用できる CPU 負荷の高い処理。コンテンツ生成、テンプレート描画、データ変換など。
- 遅いオリジンやサードパーティ API からデータを取得し、後続リクエストではその遅延を吸収したい場合。
- サーバーサイドレンダリングや静的生成のサイトで、多くのリクエストが同じレスポンスになる場合。
リクエストごとに変わるユーザー単位のレスポンス、べき等でない操作(POST、PUT、DELETE)、毎回新しく計算しなければならないレスポンスには向きません。
キャッシュを有効にすると、Cloudflare は Worker を実行する前にキャッシュを確認します。ヒットなら、キャッシュ済みレスポンスをそのまま返します。ミスなら Worker が実行され、レスポンスが Cache-Control ヘッダーの条件でキャッシュ可能なら、次のリクエスト用に保存します。
flowchart LR
accTitle: Worker リクエスト前のキャッシュの流れ
accDescr: リクエストが Cloudflare に到着し、Worker 実行前にキャッシュが参照されます。
Request["リクエスト"] --> Cache{"キャッシュ"}
Cache -- Hit --> Response["キャッシュ済みレスポンスを返す"]
Cache -- Miss --> Worker["Worker が実行される"]
Worker --> Store["レスポンスをキャッシュに保存"]
Store --> Response2["レスポンスを返す"]
Workers Caching は デフォルトで階層化 されています。Cloudflare は Worker 向けに 2 層のキャッシュを運用します。
- 下位層 — eyeball に最も近い Cloudflare データセンターのキャッシュです。その Worker のトラフィックを受けるデータセンターごとに、下位層キャッシュがあります。
- 上位層 — 下位層がミスしたときに問い合わせる、より少ないデータセンターの集合です。上位層はネットワーク全体のキャッシュ充填を集約します。
下位層でヒットすれば、そこからレスポンスを返します。ミスなら下位層が上位層に問い合わせます。上位層もミスなら、ようやく Worker がレスポンスを生成します。そのレスポンスは戻り経路で 両層 に保存されるので、以降はどのデータセンターからのリクエストでも恩恵を受けます。
flowchart LR
accTitle: Workers の階層型キャッシュ
accDescr: リクエストは下位層キャッシュ、次に上位層キャッシュ、最後に Worker の順で処理されます。
Request["リクエスト"] --> Lower{"下位層キャッシュ<br/>(eyeball の近く)"}
Lower -- Hit --> Response["キャッシュ済みレスポンスを返す"]
Lower -- Miss --> Upper{"上位層キャッシュ"}
Upper -- Hit --> Lower
Upper -- Miss --> Worker["Worker が実行される"]
Worker --> Upper
これはゾーン向け Tiered Cache と同じトポロジーで、Worker に自動適用されます。設定は不要で、Worker が Smart Placement を使っているかどうかによらず階層化されます。
これが重要な理由: 地球上のどこかで、あるキャッシュキーに対する最初のリクエストが上位層を満たします。以降のリクエストは、どの Cloudflare データセンターからでも、その場所の下位層がそのリクエストを見たことがなくても、上位層から Worker を実行せずに返せます。ヒット率は、単一の平坦なキャッシュ層より大幅に高くなります。
同じキャッシュキーへの多数のリクエストが、まだキャッシュされていない状態で Cloudflare データセンターに同時に到着すると、Cloudflare は Worker を 1 回だけ 実行し、待っているすべてのリクエストにそのレスポンスを返します。これはゾーンキャッシュと同じ リクエストコラプシング で、Workers Caching に自動適用されます。待っているリクエストは、最初のリクエストがレスポンスを出すまで、キャッシュキーごとの キャッシュロック でブロックされます。
flowchart LR
accTitle: Workers のキャッシュリクエストコラプシング
accDescr: 同じキャッシュキーへの同時リクエストは Worker 呼び出しを 1 回にし、すべてのリクエストが同じレスポンスを受け取ります。
R1["リクエスト 1"] --> Lock
R2["リクエスト 2"] --> Lock
R3["リクエスト 3"] --> Lock
Rn["..."] --> Lock
Lock{"キャッシュロック<br/>(キャッシュキーごと、データセンターごと)"}
Lock -- "最初のリクエスト" --> Worker["Worker は 1 回だけ実行"]
Worker --> Response["レスポンスを<br/>待機中の全リクエストへ<br/>ストリーム"]
これが重要な理由: リクエストコラプシングがなければ、未キャッシュの URL への急なバーストはリクエストごとに Worker を呼び、CPU 課金と Worker が呼ぶバックエンド負荷を増やします。リクエストコラプシングがあれば、そのバーストでも Worker 呼び出しは 1 回です。
覚えておく点は次のとおりです。
- コラプシングはキャッシュキーごと、データセンターごとです。 別のキャッシュキーになるリクエスト同士はまとまりません。同時にミスした 2 つのデータセンターは、それぞれ Worker を 1 回実行します(上位層がさらに集約します。階層型キャッシュ を参照してください)。
- ストリーミングレスポンスもコラプスされます。 待っているリクエストは進行中のレスポンスストリームに接続され、本文の生成と同時に受け取れます。最初のバイトを返す前に、レスポンス全体を待つ必要はありません。
- キャッシュできないレスポンスにはコラプシングは適用されません。 Worker のレスポンスがキャッシュ不可(
BYPASS、DYNAMIC)なら、リクエストごとに別の呼び出しになります。キャッシュは、保存してよいレスポンスを出すリクエストだけをまとめます。
これは Workers Caching と Cache API の大きな違いのひとつです。Cache API は同時リクエストをまとめないため、未キャッシュの URL へのバーストはリクエストごとに Worker を呼び出します。
このクイックスタートでは、キャッシュの有効化、デプロイ、動作の確認まで進めます。
{
"name": "my-worker",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-09-20",
"cache": {
"enabled": true,
},
}name = "my-worker"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-09-20"
[cache]
enabled = trueCloudflare が各レスポンスをキャッシュする時間は、max-age で制御します。
export default {
async fetch(request) {
const body = JSON.stringify({
timestamp: new Date().toISOString(),
random: Math.random(),
});
return new Response(body, {
headers: {
"Content-Type": "application/json",
// Cache for 1 hour; serve stale for up to 5 minutes while revalidating.
"Cache-Control": "public, max-age=3600, stale-while-revalidate=300",
},
});
},
};export default {
async fetch(request): Promise<Response> {
const body = JSON.stringify({
timestamp: new Date().toISOString(),
random: Math.random(),
});
return new Response(body, {
headers: {
"Content-Type": "application/json",
// Cache for 1 hour; serve stale for up to 5 minutes while revalidating.
"Cache-Control": "public, max-age=3600, stale-while-revalidate=300",
},
});
},
} satisfies ExportedHandler;Worker をデプロイします。
npx wrangler deploy続けて 2 回リクエストを送り、レスポンスヘッダーの Cf-Cache-Status を確認します。
curl -I https://my-worker.example.workers.dev/HTTP/2 200
cache-control: public, max-age=3600, stale-while-revalidate=300
cf-cache-status: MISScurl -I https://my-worker.example.workers.dev/HTTP/2 200
cache-control: public, max-age=3600, stale-while-revalidate=300
cf-cache-status: HIT2 回目のリクエストはキャッシュ済みレスポンスを受け取ります。本文の timestamp と random は 2 回のリクエストで同じです。Worker は実行のたびに新しい値を生成しますが、2 回目は Worker が実行されていないことが分かります。
- Worker の
fetchハンドラーへの HTTP 呼び出しはキャッシュ対象です。eyeball リクエスト、サービスバインディングのfetch()、ctx.exports経由のループバックfetch()を含みます。 - キャッシュされるのは
GETとHEADだけです。ほかのメソッドは常に Worker を呼び出します。同じ URL のGETとHEADは 1 つのキャッシュエントリを共有します。キャッシュキー を参照してください。 - キャッシュを通るのは
fetch()呼び出しだけです。WorkerEntrypointのカスタム RPC メソッド(例:ctx.exports.Backend.getUser(id))はキャッシュを完全にバイパスし、常に呼び出し先を実行します。処理をキャッシュしたい場合は、独自のエントリポイントのfetchハンドラーとして公開します。 - WebSocket のアップグレードリクエストはキャッシュをバイパスします。
Upgrade: websocketを付けたGETは常に Worker を呼び出します。 - ほかの呼び出し種別 —
scheduled(Cron Triggers)、queueコンシューマー、Workflows、Tail Workers、Durable Object の呼び出し、Email Workers — は常にキャッシュなしで実行されます。 - キャッシュ可否は、Worker が返すレスポンスヘッダーで決まります。Workers Caching は RFC 9111 ↗ の意味に従い、
Cache-Controlがないレスポンスの ヒューリスティック鮮度 ↗ も含みます。Cloudflare が尊重するディレクティブの一覧は Cache-Control を参照してください。 - Cloudflare の標準の キャッシュバイパス条件 が適用されます。とくに、
Set-Cookieヘッダー付きのレスポンスとAuthorizationヘッダー付きのリクエストは自動バイパスになります。 - プレビュー URL に対応しています。各プレビューは本番デプロイとは独立してキャッシュされるので、キャッシュに影響する変更をプレビューで試しても、本番のキャッシュ済みレスポンスには触れません。
- Workers for Platforms に対応しています。ユーザー Worker ごとにキャッシュがあり、ディスパッチャーや名前空間内のほかのユーザー Worker から分離されています。
Cf-Cache-Status レスポンスヘッダーで、各リクエストの結果が分かります。
よく見る値は HIT、MISS、EXPIRED、REVALIDATED、
UPDATING、STALE、BYPASS です。値の全体は Cloudflare のキャッシュ
レスポンス を参照してください。
Workers Caching は、RFC 9110 ↗ と RFC 9111 ↗ で定義された Vary ↗ レスポンスヘッダーを尊重します。Worker が Vary ヘッダーを返すと、Cloudflare は列挙したリクエストヘッダー値の組み合わせごとに別バリアントを保存し、着信リクエストのヘッダーが保存時と一致するときだけキャッシュ済みバリアントを返します。
これにより、1 つの URL で複数の表現(エンコーディング、コンテンツタイプ、言語など)をキャッシュできます。コンテンツネゴシエーションを Worker 側で手作業で調整する必要はありません。
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",
// Cache a separate variant per distinct Accept header value.
Vary: "Accept",
},
});
},
};export default {
async fetch(request): Promise<Response> {
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",
// Cache a separate variant per distinct Accept header value.
Vary: "Accept",
},
});
},
} satisfies ExportedHandler;注意点:
Vary: *はそのレスポンスのキャッシュを無効にします。ワイルドカードのばらつきはリクエストヘッダーから決定的に満たせないため、Cloudflare はレスポンスを保存しません。- バリアントはパージ目的では 1 つのキャッシュエントリを共有します。いずれかのバリアントに一致するタグやパスプレフィックスを パージ すると、その URL の全バリアントが無効になります。そのため、同じ URL の全バリアントは同じ
Cache-Tag値を使う必要があります。 Varyは、独自のバリアントをすでに作る画像変換機能(Polish、Image Resizing)とは併用できません。これらの機能が書き換えたレスポンスはVaryを無視します。- バリアントはリクエストヘッダー値の完全一致で保存されます。意味は同じでも文字列が違う値(例:
Accept-Encoding: gzip, brとAccept-Encoding: br, gzip)は別バリアントになります。バリアントの増加を抑えたい場合は、Worker が見るヘッダーを整形します(例: ゲートウェイ Worker で正規化してからリクエストを渡す)。
ある Worker が サービスバインディング で別の Worker を呼ぶとき、参照されるのは 呼び出し先 のキャッシュです。呼び出し先でキャッシュが有効で、一致するキャッシュ済みレスポンスがあれば、呼び出し元は呼び出し先を実行せずにそれを受け取ります。
flowchart LR
accTitle: Worker 間のキャッシュ
accDescr: Worker A が Worker B を呼び、Worker B の実行前に Worker B のキャッシュが参照されます。
Request["リクエスト"] --> WorkerA["Worker A"]
WorkerA --> CacheB{"Worker B のキャッシュ"}
CacheB -- Hit --> WorkerA
CacheB -- Miss --> WorkerB["Worker B"]
WorkerB --> CacheB
サービスバインディング呼び出しのキャッシュキーには、呼び出し元の ctx.props が含まれます。認可コンテキストが違う呼び出し元は、別々にキャッシュされます。詳細は キャッシュキー を参照してください。
同一アカウントの呼び出しでは、呼び出し元 Worker が個別リクエスト向けに呼び出し先のキャッシュを調整できます。cf.cacheKey でキャッシュキーを上書きするか、cf.cacheControl で Cache-Control ディレクティブを渡せます。
Durable Objects は、Workers Caching によって直接キャッシュされません。一方、Workers Caching は任意の Worker エントリポイントの手前で動くので、Durable Object を 名前付き Worker エントリポイント の背後に置き、そのエントリポイントをキャッシュすれば、Durable Object の HTTP レスポンスをキャッシュできます。
ラッパーエントリポイントはリクエストを Durable Object に転送し、返すレスポンスに Cache-Control を付けます。Workers Caching がエントリポイントの手前にあるため、以降のリクエストは Durable Object に再入せずキャッシュから返されます。
ここでのデフォルトエントリポイントは、リクエストごとに動かすべきゲートウェイです。そのためデフォルトではキャッシュを無効にし、CachedCounter では有効にします(エントリポイントごとのキャッシュ を参照してください)。
{
"name": "my-worker",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-09-20",
"cache": { "enabled": true },
"exports": {
"default": { "type": "worker", "cache": { "enabled": false } },
"CachedCounter": { "type": "worker", "cache": { "enabled": true } },
},
}name = "my-worker"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-09-20"
[cache]
enabled = true
[exports.default]
type = "worker"
[exports.default.cache]
enabled = false
[exports.CachedCounter]
type = "worker"
[exports.CachedCounter.cache]
enabled = trueimport { WorkerEntrypoint } from "cloudflare:workers";
// Cached entrypoint. Requests to this entrypoint are served from cache
// when possible; on a miss, the Durable Object is invoked and its
// response is stored.
export class CachedCounter extends WorkerEntrypoint {
async fetch(request) {
const id = this.env.COUNTER.idFromName("global");
const stub = this.env.COUNTER.get(id);
const response = await stub.fetch(request);
// Attach cache headers. Clone into a new Response so the headers
// are mutable.
return new Response(response.body, {
status: response.status,
headers: {
...Object.fromEntries(response.headers),
"Cache-Control": "public, max-age=30",
},
});
}
}
// Default entrypoint. Delegates to the cached entrypoint via ctx.exports,
// which routes through the cache.
export default {
async fetch(request, env, ctx) {
return ctx.exports.CachedCounter.fetch(request);
},
};import { WorkerEntrypoint } from "cloudflare:workers";
interface Env {
COUNTER: DurableObjectNamespace;
}
// Cached entrypoint. Requests to this entrypoint are served from cache
// when possible; on a miss, the Durable Object is invoked and its
// response is stored.
export class CachedCounter extends WorkerEntrypoint<Env> {
async fetch(request: Request): Promise<Response> {
const id = this.env.COUNTER.idFromName("global");
const stub = this.env.COUNTER.get(id);
const response = await stub.fetch(request);
// Attach cache headers. Clone into a new Response so the headers
// are mutable.
return new Response(response.body, {
status: response.status,
headers: {
...Object.fromEntries(response.headers),
"Cache-Control": "public, max-age=30",
},
});
}
}
// Default entrypoint. Delegates to the cached entrypoint via ctx.exports,
// which routes through the cache.
export default {
async fetch(request, env, ctx): Promise<Response> {
return ctx.exports.CachedCounter.fetch(request);
},
} satisfies ExportedHandler<Env>;ゲートウェイエントリポイントとキャッシュ済みの内側エントリポイントを組み合わせるパターンは、例 を参照してください。
Smart Placement は、Worker が 実行される場所 を動かします。遅いオリジンやデータベースの近くになることが多いです。キャッシュは動きません。Workers Caching は、Smart Placement の有無に関係なく、上の 階層型キャッシュ のとおり、eyeball 近くの下位層とネットワークを集約する上位層を常に持ちます。
キャッシュは、Smart Placement を検討する前に常に参照されます。具体的には次のとおりです。
- 下位層ヒット: レスポンスは eyeball に最も近いデータセンターから返されます。Worker は実行されません。Smart Placement は参照されません。
- 下位層ミス、上位層ヒット: レスポンスは上位層から返されます。Worker は実行されません。Smart Placement は参照されません。
- 両層ミス: Smart Placement が Worker の実行を配置先(例: オリジンの近く)へルーティングします。できたレスポンスは、eyeball へ戻る途中で両キャッシュ層に保存されます。
重要なのは、上位層と Smart Placement の配置先は独立した場所 であることです。上位層はネットワーク全体のキャッシュ充填を集約するよう Cloudflare が選びます。Smart Placement の配置先は、Worker とそのバックエンド間のレイテンシを最小化するために選びます。通常、同じデータセンターではありません。
flowchart LR
accTitle: 3 か所にまたがる Smart Placement 付き階層型キャッシュ
accDescr: eyeball、上位層キャッシュ、Smart Placement の配置先は独立した 3 か所です。フルキャッシュミスではこの順で経由します。
subgraph EyeballColo["eyeball 近くのデータセンター"]
Request["リクエスト"] --> Lower{"下位層キャッシュ"}
end
subgraph UpperColo["上位層データセンター"]
Upper{"上位層キャッシュ"}
end
subgraph PlacedColo["Smart Placement の配置先"]
Placed["Worker が実行される"]
Origin["オリジン / バックエンド"]
Placed <--> Origin
end
Lower -- Hit --> Response["レスポンス"]
Lower -- Miss --> Upper
Upper -- Hit --> Lower
Upper -- Miss --> Placed
Placed --> Upper
フルキャッシュミスでは、リクエストは 3 か所を経由します。eyeball 近くの下位層データセンター、上位層データセンター、Smart Placement の配置先です。キャッシュ層がこのコストを吸収するため、配置先への遅い往復はネットワーク全体で 1 回だけです。上位層が、すべての下位層ミスから配置先を守ります。
Worker は ctx.cache.purge() で、いつでも自分のキャッシュを無効化できます。タグが最も柔軟です。返すときに Cache-Tag を付け、あとからそのタグをパージします。
export default {
async fetch(request, env, ctx) {
await ctx.cache.purge({ tags: ["blog-posts"] });
return new Response("Purged", { status: 200 });
},
};export default {
async fetch(request, env, ctx): Promise<Response> {
await ctx.cache.purge({ tags: ["blog-posts"] });
return new Response("Purged", { status: 200 });
},
} satisfies ExportedHandler;ctx がスコープにない場合(ユーティリティモジュールなど)は、cloudflare:workers から cache をインポート し、cache.purge({...}) を呼べます。パージのモードとパターンの全体は キャッシュのパージ を参照してください。
Workers Cache に別料金はありません。Workers Cache を有効にすると、Worker へのすべてのリクエストが標準の Workers リクエスト料金 で課金されます。キャッシュから返す場合も Worker から返す場合も、通常のリクエストと同じリクエストあたり料金です。標準のリクエスト料金以外の課金はありません。CPU 時間は Worker が実行されたときだけ課金されます。 キャッシュヒットは CPU 時間を消費しません。
| リクエストの種類 | リクエスト料金 | CPU 時間の料金 |
|---|---|---|
キャッシュ HIT(Worker は実行されない) |
標準料金 | 課金なし |
キャッシュ MISS(Worker が実行される) |
標準料金 | 課金あり |
キャッシュ BYPASS(Worker が実行される) |
標準料金 | 課金あり |
| 静的アセットのリクエスト | 標準料金 | 課金なし |
| Worker 間の呼び出し | 標準料金 | Worker が実行された場合は課金 |
例は 料金の例: キャッシュ付き Worker を参照してください。