Cloudflare Pages はもう Workers の中に住んでいる ― Static Assets 統合後の地図
無料でエッジに静的サイトを撒けるのが気持ちよくて Cloudflare Pages を愛用してきた。最近のタイムラインで「Workers の Static Assets を使ったほうがいい」と書かれているのを何度か見かけて、引用元のポストもまさにそれだった。
Cloudflare Workersのstatic assetsを使えば100万アクセス/月あっても0円なのでオススメです
自分も wrangler を叩いてデプロイしているし、なんとなく Pages を使っている気がしているが、ワーカーズの中にページズが入っているのかいないのか、結局どっちで動いているのか、毎回曖昧なまま済ませてきた。
ここで一度きちんと整理する。結論を先に出す。
- Cloudflare Pages は廃止されない。ただし新機能は Workers にしか乗らなくなった
- Workers に Static Assets が組み込まれ、「実質 Pages」と同じことが Workers の上でできるようになった
- 静的アセットのリクエストは Pages も Workers も無料・無制限。違うのは Functions / Worker code を呼んだ時の課金枠の数え方
- 自分の 2 プロジェクトのうち、mdx-playground は完全 SSG だから移行しなくても無料。eurekapu-nuxt4 は SSR が混じるので Functions 課金枠が走るが、
_routes.jsonの exclude を入れてあるので静的アセット分は Functions を経由していない。Workers に移しても同じ最適化が効く
Before / After の地図
左が従来の Pages、右が Workers + Static Assets の構成。図の見どころは 3 つだけだ。
- どちらも「静的アセット」と「Worker(=SSR/API)」の組み合わせでできている。製品名が違うだけで、中身は同じ部品
- Pages は
_routes.jsonで「Functions を通す URL / 通さない URL」を仕分ける。Workers は逆に既定で静的アセットが優先され、必要な URL だけrun_worker_firstで Worker を先に呼ぶ - 無料枠の境目は同じ。静的アセットへのリクエストはどちらも無制限・無料。課金枠を消費するのは Worker コードを呼んだ時だけ
つまり「Pages の上に Functions(= Worker)が乗っている」のと「Workers の中に Static Assets が同梱されている」のは、責任の所在を反転させただけで、構成図としてはほぼ同じになる。
Cloudflare の Workers チームリードである Kenton Varda が「Pages 固有の機能を一般的な Workers の機能に変換している」と発言していて(→ Cloudflare Pages vs Workers 2026 / cogley.jp)、これが現状を一番うまく要約している。Pages は殺されるのではなく、Workers の中に吸収されつつある。
「廃止される」ではなく「飲み込まれる」
廃止と統合は意味が違うので、分けて書く。
- 既存の Pages プロジェクトは当面そのまま動き続ける。マイグレーション期限はまだ発表されていない
- ただし、新機能(Durable Objects、Cron Triggers、Queues、Workflows、Tail Workers、Smart Placement など)は最初から Workers にしか乗らない
- そのため、いま新規プロジェクトを始めるなら最初から Workers + Static Assets を選ぶのが推奨経路になる
ここを誤解して「Pages はもう使えない」と書くと言い過ぎになる。逆に「Pages のままでもいい」と書くと、新機能が必要になった瞬間に詰む。いまは「動いてるものは動かしておけ、新しく作るなら Workers で」が落としどころになっている。
無料枠の現在地
「無料」の話が曖昧になりがちなので、自分が触る範囲だけ整理する。
- 静的アセット:Pages、Workers どちらも リクエスト数も帯域も無制限・無料。これが catnose 氏のポストで「100 万アクセス / 月でも 0 円」と書かれている根拠
- Worker code(= SSR / API):Free プランで 10 万 req / 日、Paid($5 / 月)で 1000 万 req / 月 が基本枠
- 転送量:Cloudflare は egress を課金しない(これは Pages 時代から)
つまり「サーバー処理が要らない静的サイト」は何アクセスあろうが完全無料、というのが Cloudflare 流のオフロード設計だ。自分の mdx-playground は 1000 本超の記事を SSG で全部 HTML 化しているので、ここの無料枠にきれいに乗っている。
課金の単位は「ファイルサイズ」ではなく「isolate を起こした回数」
ここまで読んで「画像 1 枚を返すだけで Worker invocation 課金枠が走るって、Cloudflare の中で何がそんなに起きているのか」が腹落ちしないと、_routes.json の exclude を入れる動機もぼんやりする。中で何が起きているかを切り分けて書く。
Cloudflare の各 PoP(世界 300 拠点ほど)にはリクエストを受けたあとの分岐が物理的に 2 本ある。
(A) 静的アセット配信レーン Cloudflare の内部分散ストレージから、ファイルのバイト列をそのまま返す。実装は Cloudflare ネイティブの C++/Rust の HTTP サーバで、リクエストごとに JS ランタイムを起こさない。ここを通る分には Worker invocation はゼロ。
(B) Worker レーン
V8 isolate(Chrome の JS エンジンを軽量隔離したコンテナ)をスピンアップして、自分の書いた export default { fetch(req, env) { ... } } を実行する。レスポンスは isolate が組み立てて返す。この isolate が一度起動したら invocation 1。
Pages では、リクエストがエッジに着くとまず _routes.json の include / exclude を見て、「この URL は (A) か (B) か」を決める。
{
"version": 1,
"include": ["/*"], // 既定: 全部 (B) Worker レーン
"exclude": ["/_nuxt/*", "/images/*"] // ここだけ (A) 静的レーン
}
eurekapu-nuxt4 の nuxt.config.ts で _routes.json の exclude に /images/* と /_nuxt/* を入れているのは、まさにこの分岐を Edge に教えるためのもので、入れ忘れると画像も CSS も JS も全部 Worker レーン経由になる。
画像 1 枚で課金される理由
Worker レーンに入ったときに、Nuxt SSR が画像を返す経路で実行されるコードは、結局のところ次の 1 行に近い。
export default {
async fetch(request, env) {
return env.ASSETS.fetch(request); // 内部の静的ストレージを叩いて画像を返すだけ
}
}
ASSETS.fetch(...) は内部で (A) の静的ストレージを叩きに行くので、転送量は egress 無料、画像のバイト数も無料。それでもエッジは
- V8 isolate を起こす
- fetch handler を呼ぶ
- レスポンスをストリームに書き戻す
- isolate を解放する
を必ず通る。Cloudflare の課金体系は、この一連を「Worker invocation 1」とカウントする。中身が画像か API か SSR か、CPU をどれだけ食ったかは別の枠(CPU time)で計測されるが、invocation カウントは「isolate を呼んだか / 呼ばなかったか」だけで決まる。
つまり、画像本体のバイト数ではなく、画像を返すために isolate を 1 回起こした事実そのものが課金単位になる。1000 アクセスで画像 30 枚を Worker レーン経由で返したら、invocation が 30,000 回分溶ける。exclude を入れて画像 URL を (A) に逃がすと、Edge は「この URL は isolate を起こさないでいい」と判断するので 0 のまま。
Workers + Static Assets で既定が逆になっている理由
このカラクリがわかると、Workers + Static Assets で既定の振り分けが逆になっている理由も自然に読める。
- Pages:
include: ['/*']が既定 → 既定が (B) Worker レーン → 静的にしたい URL をexcludeで外す - Workers: 静的アセット配信が既定 → 既定が (A) 静的レーン → Worker を走らせたい URL を
run_worker_firstで指名する
Cloudflare 自身が「Pages の既定は安全側じゃなかった(うっかり全 URL を Worker 経由にすると課金枠を無駄に食う)」と認めて、Workers + Static Assets ではデフォルトを反転させた。これが Pages → Workers 統合の背景にある実務的な動機の 1 つになっている。
自分の 2 プロジェクトはどうなっているか
mdx-playground(log.eurekapu.com)
apps/web/nuxt.config.ts の Nitro 設定はこうなっている。
nitro: {
preset: "cloudflare-pages-static",
prerender: {
crawlLinks: true,
routes: ['/blog', '/', '/beat-monitoring', '/memory-makers'],
ignore: [/\?/, '/search', ...],
failOnError: false,
},
},
cloudflare-pages-static は Pages のうち「静的アセットだけ」を使うプリセット。Functions は一切作られず、dist/ には HTML / CSS / 画像しか入らない。動的処理は _redirects の単純な URL 書き換えだけで、それも Cloudflare の Pages 側で評価される(コンテナを起こさない)。
ということは、Worker code が呼ばれる回数は実質ゼロで、無料枠も消費していない。Workers + Static Assets に移しても挙動も料金も変わらないから、急いで移行する理由はない。新しいブログを作るときに最初から Workers preset を選べばいい、くらいの優先度になる。
eurekapu-nuxt4(info-accounting.com)
こっちは認証あり・Stripe あり・D1 ありの本格的なアプリで、nuxt.config.ts がこうなっている。
nitro: {
preset: 'cloudflare_pages',
sourceMap: false,
cloudflare: {
pages: {
routes: {
include: ['/*'],
exclude: [
'/_nuxt/*',
'/images/*',
'/favicon.ico',
'/robots.txt',
...redirectExcludes,
],
},
},
},
},
cloudflare_pages は SSR 込みの Pages プリセット。_routes.json の exclude で「_nuxt/* と images/* は Pages Functions を通さない(=静的アセットとして直接配信する)」と明示している。
この exclude を入れた理由がまさに今回の話と地続きで、何も指定しないと「全 URL が Pages Functions(= Worker)経由」になってしまい、画像 1 枚配るだけで Worker invocation 課金枠を消費する。_routes.json の exclude で「静的でいいものは静的に流す」と切り分けることで、無料枠を温存できる。
これを Workers + Static Assets に移すと、考え方が逆になる。
- Pages:「全部 Functions、ただし
excludeで静的に逃がす」 - Workers:「全部静的、ただし
run_worker_firstで SSR を挟む」
責任のデフォルトが反転するだけで、最適化の発想は同じだ。だからこの構成も、いますぐ移す必要はない。ただし Durable Objects や Cron が欲しくなった瞬間に Workers 側でしか書けないので、機能要件が伸びれば移行が前提になる。
「Workers の中にページ図が入ってる」の腹落ち
最初に持っていた素朴な疑問「ワーカーズの中にページ図が入っているのか?」は、半分正解で半分逆だった。
- 正解の半分:いまの Workers には Pages 相当の Static Assets 配信が入っている。Pages でできていたことは Workers でほぼ全部できる
- 逆の半分:歴史的には Pages のほうが先で、Pages の中に Functions(= Worker)が住んでいた。それを Workers 側に移して、「Workers の中に Static Assets が住む」構造に組み直しているのが現在進行形のリファクタリング
つまり「Pages も Workers も静的+関数のセット」という構成自体は一貫していて、どっちのブランドの皮を被るかだけが変わっている。新しい皮(Workers)に未来の機能が乗ってくるから、新規はそちら、というのが Cloudflare の言いたいこと。
自分の運用への落とし込み
ここまで踏まえて、自分はこう動くつもりでいる。
- mdx-playground:このまま
cloudflare-pages-staticを続ける。完全 SSG なので Workers に移しても得るものがない。むしろビルドパイプラインを壊すリスクのほうが大きい - eurekapu-nuxt4:当面 Pages のままだが、Cron や Durable Objects を使った機能(決算ビートの定期取得など)を新規に作るときは、そのモジュールだけ別 Worker として切り出す。アプリ本体まで含めた全面移行は急がない
- これから作る新規プロジェクト:最初から Workers + Static Assets で組む。preset は Nuxt なら
cloudflare-module系、フレームワークなしならwrangler initの Static Assets テンプレ
「Pages 推奨だったはずなのに気付いたら Workers 推奨になってた」と感じたのは、Cloudflare 自身がブランドを差し替えに行っている最中だからで、自分の感覚は間違っていなかった。ただ、慌てて全部移す必要もない。動いているものはそのまま、新しいものは Workers で、で十分間に合う。