- Workers の static assets 機能で静的サイトをデプロイし、キャッシュと課金の仕組みを説明できる
not_found_handling/html_handling/run_worker_firstでルーティング挙動を制御できる_headers/_redirectsファイルでレスポンスヘッダーとリダイレクトを宣言的に管理できる- 静的サイトと API を 1 つの Worker にまとめたフルスタック構成を組める
- Cloudflare Pages との関係を理解し、新規開発でどちらを選ぶべきか判断できる
Workers の静的アセット機能
前章までの Worker は「コードだけ」をデプロイしていました。Workers の static assets 機能を使うと、HTML・CSS・画像などの静的ファイルを Worker の一部としてアップロードでき、キャッシュと配信は Cloudflare に任せられます。Worker コードと静的アセットは 1 回の wrangler deploy で単一のユニットとしてデプロイされます。
中心になるのは Wrangler 設定ファイルの assets.directory です。ここで指定したフォルダ(フレームワークなら ./public/ や ./dist/、./build/ が定番)の中身が、デプロイ時に Wrangler によって自動アップロードされます。main を持たない「アセットだけの Worker」も作れます。
// wrangler.jsonc — アセットのみの最小構成
{
"name": "my-static-site",
"compatibility_date": "2026-08-11",
"assets": {
"directory": "./public"
}
}
配信面の特徴は次の 3 点です。
- アセットへのリクエストは無料・無制限 — 課金対象になるのは Worker スクリプトが呼び出されたときだけで、アセットの保存自体にも追加コストはありません(2026-08 時点)。
- 自動キャッシュ — 一度リクエストされたアセットは最寄りの Cloudflare ロケーションにキャッシュされ、tiered cache により他のデータセンターからも近くのキャッシュ経由で取得されます。
- 既定のレスポンスヘッダー —
Content-Typeはファイル拡張子から自動判定され、Cache-Control: public, max-age=0, must-revalidateとETagが付きます。ブラウザは毎回鮮度を確認しますが、変更が無ければ再ダウンロードは発生しないため、高速さと「古いコンテンツを配らない」ことを両立します。
主な制限は次のとおりです(2026-08 時点)。1 つの Worker に設定できるアセットのコレクションは 1 つだけで、Paid プランのファイル数上限を使うには Wrangler v4.34.0 以降が必要です。
| 項目 | Workers Free | Workers Paid |
|---|---|---|
| Worker バージョンあたりのファイル数 | 20,000 | 100,000 |
| ファイル 1 個のサイズ上限 | 25 MiB | 25 MiB |
ルーティング挙動を制御する
静的アセットと Worker スクリプトが同居するとき、リクエストは既定で次の順に処理されます。
- リクエストパスがアセットディレクトリ内のファイルに一致すれば、Worker スクリプトを起動せずにそのファイルを配信する(課金なし)
- 一致しなければ Worker スクリプトを呼び出す
- Worker スクリプトが無ければ
404 Not Found(本文なし)を返す
Worker スクリプトからは env.ASSETS.fetch(request)(assets binding)でアセット配信へ処理を委譲できます。URL のホスト名部分は無視され、パス名だけがアセットの照合に使われます。この「アセット優先」の既定挙動は、assets 配下のオプションで変更できます。
env.ASSETS.fetch() は同じ配信処理に戻る経路ですが、そのリクエストはすでに Worker が起動しているため課金対象です。not_found_handling — SPA フォールバックとカスタム 404
| 設定値 | アセット不一致時の挙動 | 主な用途 |
|---|---|---|
| (未設定) | Worker スクリプトを呼ぶ。無ければ 404 | フルスタック・API 主体の構成 |
"single-page-application" | /index.html を 200 OK で返す | React / Vue などの SPA |
"404-page" | 最寄りの 404.html を 404 で返す | SSG・ドキュメントサイト |
SPA モードでは、/dashboard のようにアセットへ一致しない URL への直アクセスにも /index.html が 200 で返り、クライアントサイドルーターが処理を引き継げます。Pages はプロジェクト構成からこの挙動を自動判定していましたが、Workers では誤設定防止のため明示的に設定します。
html_handling — 末尾スラッシュの正規化
既定の auto-trailing-slash は、単独ファイル foo.html を /foo で、フォルダ配下の folder/index.html を /folder/ で配信し、/foo.html や /folder のような非正規 URL は 307 で正規 URL にリダイレクトします。force-trailing-slash(常にスラッシュ付き)・drop-trailing-slash(常にスラッシュなし)・none(正規化を無効化)も選べます。SEO 上の重複コンテンツ問題を防ぐ仕組みです。
run_worker_first — Worker を先に実行する
run_worker_first: true を設定すると、アセットに一致するリクエストでも常に Worker スクリプトが先に実行されます。認証チェック・リクエストログ・HTMLRewriter によるアセット加工といった「ミドルウェア」を実装したいときに使います。配列でパスパターンを渡すと対象ルートを絞り込めます(glob の * と ! による除外パターンに対応。Wrangler v4.20.0 以降)。除外パターンが優先され、記述順は評価に影響しません。
run_worker_first は経路に Worker を 1 段割り込ませ、401 で止める辺を増やす代わりに、無料で返せていた経路を課金対象に変えます。だからこそ対象パスは配列で絞ります。// wrangler.jsonc — SPA + API の典型構成
{
"name": "my-spa-worker",
"main": "./src/index.ts",
"compatibility_date": "2026-08-11",
"assets": {
"directory": "./dist",
"not_found_handling": "single-page-application",
"binding": "ASSETS",
"run_worker_first": ["/api/*", "!/api/docs/*"]
}
}
navigation リクエストの最適化
Worker スクリプトがあり、not_found_handling を設定していて、compatibility_date が 2025-04-01 以降(または assets_navigation_prefers_asset_serving フラグ有効)の場合、ブラウザのページ遷移で送られる navigation リクエスト(Sec-Fetch-Mode: navigate ヘッダー付き)は Worker スクリプトを呼ばずにアセットを配信します。SPA のページ遷移のたびに課金対象の Worker 起動が発生するのを防ぐ最適化です。不要なら assets_navigation_has_no_effect フラグで無効化できます。
_headers と _redirects で配信を宣言的に制御する
静的アセットディレクトリの直下に _headers / _redirects という拡張子なしのプレーンテキストファイルを置くと、Workers がそれらを解釈し、静的アセットの応答にカスタムヘッダーとリダイレクトを適用します。ファイル自体は静的アセットとして配信されません。フレームワーク利用時は、ビルドで出力ディレクトリへコピーされる public/ や static/ に置くのが定石です。
_redirects はアセット照合より手前にあるので、アセットが存在しないパスでも 301/302 が返り Worker まで届きません。逆に Worker まで落ちたリクエストは _headers の段を通らないので、API や SSR のヘッダーは Worker のコードで付けます。_headers
「URL パターン 1 行 + インデントしたヘッダー行」のブロックで記述します。複数ルールに一致したリクエストはすべてのルールのヘッダーを継承し、既定ヘッダーの除去は ! (感嘆符 + 空白)をヘッダー名に前置します。
# ハッシュ付きファイル名のアセットはブラウザに長期キャッシュさせる
/assets/*
Cache-Control: public, max-age=31556952, immutable
# 全体に CSP を掛けつつ、JPEG 画像からは除去する
/*
Content-Security-Policy: default-src 'self';
/*.jpg
! Content-Security-Policy
ルールは最大 100 個、1 行あたり 2,000 文字までです(2026-08 時点)。同じヘッダーが複数回適用された場合、値はカンマで結合されます。
_redirects
1 行につき [source] [destination] [code?] を記述します。ステータスコードを省略すると 302 になります。*(splat、転送先では :splat で参照)と :placeholder が使え、ステータスコードに 200 を指定すると同一サイト内のプロキシ(rewrite)として動作します(外部ドメインへのプロキシは不可)。
# 恒久移転は 301 を明示(省略時は 302)
/old-top / 301
/blog/* https://blog.example.com/:splat
/products/:code /items/:code 301
静的リダイレクト 2,000 件 + 動的リダイレクト 100 件の計 2,100 件まで、1 ルール 1,000 文字までです(2026-08 時点)。同一 source に複数ルールがある場合は上にある行が優先され、リダイレクトはヘッダー適用より先に評価されます。またリダイレクトはアセット照合よりも手前で評価されるため、一致するアセットが無いパスでも適用されます。なお、この 2 ファイルのルールが働くのは静的アセット配信の経路だけで、Worker コードが生成したレスポンスには効きません(詳細はつまずきポイント参照)。
フレームワークのデプロイと Pages との関係
React や Astro などのフレームワークで作るサイトも、ビルド出力は「静的アセット(+ SSR 用の Worker スクリプト)」なので同じ仕組みでデプロイできます。C3 の --framework フラグを使うと、フレームワーク込みのプロジェクトを一発で scaffold できます。たとえば React なら npm create cloudflare@latest -- my-react-app --framework=react です。生成されるテンプレートでは worker/index.ts が API バックエンドになり、assets.not_found_handling は single-page-application に設定済み、Cloudflare Vite プラグインにより本番同等の Workers ランタイムでローカル開発できます。Astro・React Router(旧 Remix)・Next.js・Nuxt・SvelteKit など主要フレームワークには Workers 向けガイドとアダプタが用意されています。
Cloudflare Pages との使い分け
Cloudflare Pages は、Git 連携による push 時の自動ビルド・デプロイとプレビュー環境を備えたホスティングサービスで、サーバー処理は Pages Functions が担ってきました。現在は Workers の static assets が Pages の主要機能(無料の静的配信・_headers / _redirects・SPA フォールバック・ASSETS binding・Git 連携ビルド)をカバーしたうえで、Workers でしか使えない機能が多くあります。
| 機能 | Workers | Pages |
|---|---|---|
| Cloudflare Vite プラグイン | 対応 | 非対応 |
| Gradual Deployments(段階的デプロイ) | 対応 | 非対応 |
| Cron Triggers | 対応 | 非対応 |
| Workers Logs / Logpush | 対応 | 非対応 |
| Durable Objects | 対応 | 別 Worker 経由の回避策のみ |
| Queues のコンシューマ | 対応 | 非対応 |
| Cloudflare 外ネームサーバーのカスタムドメイン | 非対応 | 対応 |
公式ドキュメントには Pages から Workers への移行ガイドと機能互換マトリクスが用意されており、Pages docs 側からも「Migrate to Workers」として案内されています。新規開発では Workers を選ぶのが現在の流れです。移行の要点は「pages_build_output_dir を assets.directory へ」「Pages Functions を Worker スクリプトへ」「wrangler pages dev / deploy を wrangler dev / deploy へ」の置き換えです。advanced mode の _worker.js を使っていた場合は、静的アセットとして公開されないよう .assetsignore(.gitignore と同形式)で除外するか、アセットディレクトリの外へ移動します。なお、Pages より前の世代の仕組みである Workers Sites は新規プロジェクトで使わないでください。
ここからのハンズオンでは、静的サイトのデプロイから始めて、「静的サイト + API を 1 つの Worker で配信する」構成までを実際に作ります。3 本とも Free プランで完走できます。
C3 の Static site テンプレートから静的サイトを workers.dev に公開します。所要時間の目安は 10 分です。
- プロジェクトを作成する。プロンプトでは
Hello World example→Static site→TypeScript→ git はYes→ デプロイはNoを選ぶnpm create cloudflare@latest -- my-static-site cd my-static-site - ローカル開発サーバーを起動し、ブラウザで表示を確認する
npx wrangler dev # → http://localhost:8787 で配信される public/index.htmlを編集して保存し、リロードで反映されることを確かめたら、デプロイするnpx wrangler deploy # 出力に表示される URL にアクセスして確認する # https://my-static-site.<YOUR_SUBDOMAIN>.workers.dev
<YOUR_SUBDOMAIN> は第 1 章で設定した自分の workers.dev サブドメイン名に読み替えてください。ブラウザで開いて public/index.html の内容が表示されれば成功です。
4-1 のプロジェクトに Worker スクリプトを追加し、/api/* は API として、それ以外は静的アセットとして 1 つの Worker から配信します。所要時間の目安は 15 分です。
wrangler.jsoncにmainと assets binding を追加する{ "name": "my-static-site", "main": "./src/index.ts", "compatibility_date": "2026-08-11", "assets": { "directory": "./public", "binding": "ASSETS" } }src/index.tsを作成する。アセットに一致しなかったリクエストだけが Worker に届くので、API 以外は assets binding へ委譲するinterface Env { ASSETS: Fetcher; } export default { async fetch(request, env): Promise<Response> { const url = new URL(request.url); if (url.pathname === "/api/time") { return Response.json({ now: new Date().toISOString() }); } // アセット不一致・API 以外はアセット配信へ委譲する // (not_found_handling の挙動が適用される) return env.ASSETS.fetch(request); }, } satisfies ExportedHandler<Env>;npx wrangler devを起動し、両方の配信を確認するcurl http://localhost:8787/api/time # → {"now":"2026-08-11T03:00:00.000Z"} のような JSON curl http://localhost:8787/ # → public/index.html の内容(Worker は起動されない)npx wrangler deployでデプロイし、workers.dev の URL でも同じ挙動になることを確認する
静的ページへのリクエストは無料のアセット配信、/api/time だけが課金対象の Worker 起動になる、コスト効率のよい構成です。
4-2 のプロジェクトに 2 つの制御ファイルを追加し、ヘッダー付与とリダイレクトを確認します。所要時間の目安は 10 分です。
public/_headersを作成する/* X-Frame-Options: DENY X-Content-Type-Options: nosniffpublic/_redirectsを作成する/old-top / 301npx wrangler devを再起動し、curl -I http://localhost:8787/で 2 つのセキュリティヘッダーが付くこと、curl -I http://localhost:8787/old-topで301とlocation: /が返ることを確認するcurl -I http://localhost:8787/api/timeも実行し、Worker が生成する API レスポンスには_headersのヘッダーが付かないことを観察する
_headers / _redirects がアセット応答だけに働くこと、そして両ファイルが配信されないこと(curl http://localhost:8787/_headers が 404 相当になる)まで確認できれば完了です。
つまずきポイント
_headers / _redirects が API や SSR のレスポンスに効かない — この 2 ファイルのルールは静的アセットの応答にのみ適用されます。Worker コードが生成するレスポンス(SSR フレームワークや run_worker_first 経由を含む)には、リクエスト URL がルールに一致していても適用されません。Worker 側では Response にヘッダーを直接設定し、リダイレクトもコードで実装してください。
ブラウザで API パスを開くと JSON ではなく HTML が返る — not_found_handling 設定済み + compatibility_date が 2025-04-01 以降だと、navigation リクエストは Worker を呼ばずアセットを配信します。fetch("/api/date") のようなクライアントサイドリクエストは正常に Worker へ届くため、アプリの動作としては問題ありません。意図的な課金削減の挙動ですが、無効化したい場合は assets_navigation_has_no_effect フラグを設定します。
SPA の深いリンクに直アクセスすると 404 になる — Pages と違い、Workers は SPA かどうかを自動判定しません。assets.not_found_handling に "single-page-application" を明示していないと、アセットに一致しない URL はフォールバックされず 404(または Worker 呼び出し)になります。
Free プランで run_worker_first 対象パスが 429 を返す — run_worker_first のパターンに一致するリクエストは必ず Worker を起動するため、無料枠のリクエスト上限を超えると、アセットへフォールバックせずに 429 Too Many Requests が返ります。! で始まる除外パターンのパスは Worker を経由しないため影響を受けません。
理解度チェック
Q1. 静的アセットに一致したリクエストの課金はどうなる?
無料・無制限です。課金対象になるのは Worker スクリプトが呼び出されたリクエストだけで、アセットの保存にも追加コストはかかりません(2026-08 時点)。
Q2. SPA で、アセットに一致しない URL への直アクセスに index.html を返すための設定は?
assets.not_found_handling を "single-page-application" に設定します。アセット不一致のリクエストに /index.html が 200 OK で返ります。SSG サイトなら "404-page" で最寄りの 404.html を返せます。
Q3. アセット配信よりも先に認証チェックを実行したい。どう設定する?
assets.run_worker_first を使います。true で全リクエスト、配列(glob の * と ! 除外に対応、Wrangler v4.20.0 以降)で特定パスだけ Worker スクリプトを先に実行できます。Worker からは assets binding の env.ASSETS.fetch() でアセット配信に戻します。
Q4. _redirects でステータスコードを省略したときのデフォルトは?
302 です。恒久移転にしたい場合は行末に 301 を明示します。200 を指定すると同一サイト内のプロキシ(rewrite)になります。
Q5. _headers に書いたルールが /api/ のレスポンスに付かないのはなぜ?
_headers / _redirects は静的アセットの応答にのみ適用されるためです。Worker コードが生成するレスポンスには URL が一致していても適用されないので、Worker 内で Response のヘッダーとして付与します。
まとめと次章へ
assets.directoryを指定するだけで静的サイトを Worker として配信できる。アセットへのリクエストは無料で、保存コストもかからない- 既定のルーティングは「アセット優先 → Worker → 404」。
not_found_handlingで SPA / SSG のフォールバックを明示し、run_worker_firstで認証などの前段処理を差し込める _headers/_redirectsで静的アセット応答のヘッダーとリダイレクトを宣言的に管理できる(Worker が生成するレスポンスには効かない)- 主要フレームワークは C3 と Vite プラグインで Workers にデプロイできる。Pages には公式の移行ガイドがあり、新規開発では Workers が推奨される流れ
サイトの「ガワ」を配信できるようになったので、次章からはデータを扱います。第 5 章では、エッジで読み取り最速のグローバル key-value ストア Workers KV を学び、この章で作った構成に動的なデータを持たせていきます。
参考リンク: