[{"data":1,"prerenderedAt":357},["ShallowReactive",2],{"content-/cloudflare-pages-workers-bundle-architecture":3,"all-pages-for-dir":355,"og-image-/cloudflare-pages-workers-bundle-architecture":356},{"id":4,"title":5,"body":6,"category":335,"description":336,"extension":337,"meta":338,"navigation":339,"ogImage":340,"path":341,"project_name":342,"published":343,"publishedAt":344,"seo":345,"stem":346,"tags":347,"todo":340,"unpublished":343,"updatedAt":340,"__hash__":354},"pages/2026-06/2026-06-16/cloudflare-pages-workers-bundle-architecture.md","Cloudflare Pages の 1MB Worker bundle 制限に当たって構成図A〜Dを書きながら設計判断を整理した",{"type":7,"value":8,"toc":317},"minimark",[9,13,26,34,38,53,70,74,81,86,92,96,99,103,106,110,128,135,139,146,150,161,172,176,179,226,236,240,254,269,280,283],[10,11,12],"h2",{"id":12},"起こったこと",[14,15,16,17,21,22,25],"p",{},"朝、昨日のデプロイ失敗ログを開き直したところから始まった。prerender が全件 500 で落ちていた現象は、",[18,19,20],"code",{},"auth.server.ts"," プラグインが prerender 時にも走って、better-auth が D1 にアクセスしようとして例外を投げていたのが犯人だった。",[18,23,24],{},"fetchSession()"," を prerender 中はスキップするように直したら、51 routes すべて 200 で返るようになった。",[14,27,28,29,33],{},"ところが次に ",[30,31,32],"strong",{},"Worker bundle のサイズ","でぶつかった。非圧縮 16 MiB、gzip 後 3.18 MiB。Cloudflare Pages の Worker bundle は無料で 1 MiB、有料 ($5/月) で 10 MiB が上限と Codex に確認したら、思った以上に天井が低いとわかった。「5ドル払ったとして 10MiB にしか増えない」と気づいた瞬間、当初の方針を捨てて構成自体を見直す方向に切り替わった。",[10,35,37],{"id":36},"ssr-で粘ったがダメだった経路","SSR で粘ったがダメだった経路",[14,39,40,41,44,45,48,49,52],{},"最初に試したのは ",[18,42,43],{},"routeRules"," で ",[18,46,47],{},"/takken/**"," を ",[18,50,51],{},"ssr: false"," にする案。i18n の routeRules 書き換えで glob が効かなかったので、51 件の具体パスに展開してリトライ。それでも Worker bundle は 3.175 MiB のまま落ちなかった。",[14,54,55,56,59,60,62,63,69],{},"その次に各ページの ",[18,57,58],{},"definePageMeta"," に ",[18,61,51],{}," を sed で一括注入した。これも効かなかった。Codex に状況を投げて返ってきた結論は明確で、",[30,64,65,66,68],{},"Nuxt の ",[18,67,51],{}," はリクエスト時 SSR をスキップするだけで、ビルド時に Worker bundle からコードを除外しない","。58 個別チャンク (非圧縮 ≒ 1 MB、圧縮後 ≒ 170 KB) は SSR をやめても bundle に焼き込まれ続ける。「ビルド時に Worker から完全除外する公式手段は無い」と書かれていて、SSR で粘る発想を一度全部捨てる必要があった。",[10,71,73],{"id":72},"プラン-ad-を構成図に起こした","プラン A〜D を構成図に起こした",[14,75,76,77,80],{},"ここで頭の中の見取り図が破綻したので、HTML メモを切って構成図を 4 枚描いた。",[18,78,79],{},"memo/2026-06-16/cloudflare-architecture-options.html"," に保存し、ローカルサーバーを立てて行ったり来たりした。",[82,83,85],"h3",{"id":84},"plan-a-takken-だけ別-nuxt-サブビルドで静的化","Plan A: takken だけ別 Nuxt サブビルドで静的化",[14,87,88,91],{},[18,89,90],{},"/takken/"," 配下を独立した Nuxt プロジェクトとしてビルドし、Cloudflare Pages の別プロジェクトに割り当て、本体側はリバプロでルーティングする案。Worker bundle は確実に分離できるが、認証セッションを別ドメイン (もしくは別 Worker) と共有する仕組みを別途設計する必要があり、構成の複雑さが一段増える。",[82,93,95],{"id":94},"plan-b-takken-を-spa-化して-cdn-に投げる","Plan B: takken を SPA 化して CDN に投げる",[14,97,98],{},"Plan B は最初から候補にしていたが、認証付きのページを SPA で配ると初回表示で「白画面 + ローディング → セッション判定 → 出し分け」のフローが必須になり、SEO もユーザー体験も劣化する。ユーザーから「Plan B はありえない」と即却下された。",[82,100,102],{"id":101},"plan-c-フロントとバックエンドを別リポに割る","Plan C: フロントとバックエンドを別リポに割る",[14,104,105],{},"Codex 調査でも触れた構成で、よく「CGM 系のサイト (note.com 等) はこうしている」と説明される形。フロントは静的サイト生成器で吐き、バックエンドは API リポジトリとして独立させる。長期的には強いが、今のリポを縦に切り直すコストが大きく、認証フローも一度全部書き直す。今日この場で着地させるには手が遠い。",[82,107,109],{"id":108},"plan-d-htmljs-直配信ssg-メインで対象オブジェクトはオブジェクトストレージへ","Plan D: HTML/JS 直配信、SSG メインで対象オブジェクトはオブジェクトストレージへ",[14,111,112,113,115,116,119,120,123,124,127],{},"途中で「いや、",[18,114,90],{}," の本文はそもそも Vue ファイルである必要があるのか？」という問いが出てきた。HTML として吐いたものを ",[18,117,118],{},"public/content/takken/{slug}.html"," のように置き、動的ルートが fetch して ",[18,121,122],{},"v-html"," で流し込めば、",[30,125,126],{},"Worker bundle にコードが焼き込まれない","。これが Plan D の核だった。後で本数が増えたら R2 などのオブジェクトストレージに逃がす道も自然に開く。",[14,129,130,131,134],{},"ここで「マークダウンにすれば自動で R2 に行く」と誤解しかけたが、マークダウン化と R2 移行は別の話だ。HTML 化は「Worker bundle から本文を抜く」目的、R2 移行は「",[18,132,133],{},"public/"," が肥大したら逃がす」目的。HTML メモにも「マークダウン化 ≠ R2 移行 (よくある誤解)」セクションを切って明示した。",[10,136,138],{"id":137},"vue-ファイルでもいいじゃんがダメな理由","「Vue ファイルでもいいじゃん」がダメな理由",[14,140,141,142,145],{},"途中で「PageView の Vue ファイルでもいいんじゃない？」という方向に揺り戻しがあった。これは今日の 3 MiB 超過の元凶そのもので、Vue ファイルとして書くと ",[30,143,144],{},"そのページが import した API client・auth client・ストアまで芋づる式に Worker bundle に焼き込まれる","。本文だけ HTML としてビルド時に切り出し、Vue 側は薄い動的ルートに留めれば、bundle に乗るのは「動的ルート 1 本ぶん」で済む。Nuxt と Vue を捨てるわけではなく、共通レイアウト・ヘッダ・フッタは layouts と composable で再利用する。本文だけ HTML で外出しする、という線引きに落ち着いた。",[10,147,149],{"id":148},"phase-1-計画書を書いて-codex-に-3-ラウンドかけた","Phase 1 計画書を書いて Codex に 3 ラウンドかけた",[14,151,152,153,156,157,160],{},"Plan D で進めると決まったところで、計画書を ",[18,154,155],{},"plan-d-implementation-plan.html"," として書き起こした。Codex に投げたら 4 点指摘が返ってきて、Step 3-1 の対象ファイル指定 (",[18,158,159],{},"[...slug].vue",") を修正、期間表記の削除、Phase 1 を takken に絞る再構成、テスト戦略の追加、で v1.2 に直した。",[14,162,163,164,167,168,171],{},"ユーザーから「Phase 2/3 も書き切らないと Phase 1 の妥当性は評価できない」と指摘が入り、",[18,165,166],{},"plan-d-phase2-implementation-plan.html"," (lessons 移行) と ",[18,169,170],{},"plan-d-phase3-implementation-plan.html"," (LP・固定ページ) も追加で書いた。書いてみると Phase 1 に先回りで組み込むべき調整事項が 5 つ洗い出され、特に「component transform registry」「Phase 3 LP は静的本文だけ外出し、動的部品は Vue 側」あたりは Phase 1 の API パスと composable の設計に直接効いた。Codex のレビューを 3 ラウンド回して「OK 致命点なし」を獲得した。",[10,173,175],{"id":174},"phase-1-実装-step-04-のデプロイとロールバック","Phase 1 実装: Step 0〜4 のデプロイとロールバック",[14,177,178],{},"午後セッションで Step 0〜4 を一気通貫で実装した。",[180,181,182,189,211,220],"ul",{},[183,184,185,188],"li",{},[30,186,187],{},"Step 0",": ブランチ切替、判定基準ドキュメントとテスト雛形を並列で作成",[183,190,191,194,195,198,199,202,203,206,207,210],{},[30,192,193],{},"Step 1.5 POC",": ",[18,196,197],{},"useArticleDomHooks"," composable を抽出、",[18,200,201],{},"useContent"," と page-content API を作成、変換スクリプトの骨格と動的ルートを実装、",[18,204,205],{},"dairi.html"," と ",[18,208,209],{},"_meta.json"," を手作業で 1 件だけ作って判定 3 項目を実測。すべて OK",[183,212,213,216,217,219],{},[30,214,215],{},"Step 2",": 残り 48 件を変換スクリプトで一括変換。途中でテンプレリテラル展開漏れと ",[18,218,209],{}," 上書きでエントリが消える事故があり、git restore して再変換。最終的に Worker bundle 2.909 MiB で 3 MiB 上限をクリア",[183,221,222,225],{},[30,223,224],{},"Step 3",": Vitest 全 2526 件 pass、Playwright は環境問題で 44 件 failed だったので curl の smoke test 49 件に切り替えて全 200 を確認",[14,227,228,229,231,232,235],{},"ここで節目ごとに止まり、ユーザーに承認を貰ってから次に進む段取りにしておいた。POC 完了時に「表示を確認していますか？」と突っ込まれ、ビルド成果物の構造判定だけで OK 報告していたことが露呈した。dev サーバーを起動して curl で叩き、ブラウザでスクショまで撮ったら、dairi で右サイド TOC が出ていない regression を発見した。",[18,230,122],{}," で挿入された article の h2/h3 を ",[18,233,234],{},"setupToc"," が拾えていなかったので MutationObserver を追加して直した。「ビルドが通った」と「画面が出ている」を分けて確認する大切さを、また 1 回叩き込まれた。",[10,237,239],{"id":238},"本番デプロイ-404-ロールバック-codex-診断","本番デプロイ → 404 → ロールバック → Codex 診断",[14,241,242,245,246,249,250,253],{},[18,243,244],{},"scripts/deploy.ps1"," を見落としていて、ユーザーに「これでしょ」と指摘されて思い出した。",[18,247,248],{},"wrangler pages deploy --branch main"," で本番反映、4.8 分で ",[18,251,252],{},"https://eurekapu-nuxt4.pages.dev"," にデプロイ成功。",[14,255,256,257,260,261,264,265,268],{},"ところが本番で 49 件すべて 404 が返ってきた。API は 200 で返るのに ",[18,258,259],{},"/takken/*"," の SSR ルーティングだけ通らない。一度ロールバックを試みたが、ロールバック前の状態が元々 Worker サイズ超過で deploy できなかったので、当然ロールバックの deploy も失敗した。「ロールバックしてデプロイ成功するわけない、それが原因でやってるんだから」とユーザーに正面から指摘され、Codex に原因を深掘りさせる方向に切り替えた。指示書を memo に書き、",[18,262,263],{},"codex exec"," で直接 CLI を叩いた。Codex から致命的原因が明確に返ってきて、修正を 2 ファイルに当てた後、",[18,266,267],{},"wrangler pages dev"," で本番相当ランタイムで検証し、49 件すべて 200 を確認した。",[14,270,271,272,275,276,279],{},"ところが main にマージし直したら ",[30,273,274],{},"48 件の vue chunk が復活","して Worker サイズが再超過。ロールバックの revert が部分的に残っていて、削除したはずの vue ファイルが戻ってきていた。ここで時間切れ。引き継ぎ memo (",[18,277,278],{},"memo/2026-06-16/plan-d-deploy-failure-handoff.md",") を書いて commit し、deploy 失敗状態を明記したまま終了した。",[10,281,282],{"id":282},"学び",[180,284,285,291,299,305,311],{},[183,286,287,290],{},[30,288,289],{},"Cloudflare Pages の Worker bundle 上限は思ったより低い",": 無料 1 MiB、有料でも 10 MiB。「サーバーは贅沢に使える」前提だと早晩ぶつかる",[183,292,293,298],{},[30,294,65,295,297],{},[18,296,51],{}," は Worker bundle 削減には効かない",": リクエスト時 SSR をスキップするだけ。bundle から物理的にコードを抜くには「Vue ファイルとして import される経路を断つ」必要がある",[183,300,301,304],{},[30,302,303],{},"構成図を描くと判断が早くなる",": 頭の中だけで 4 案を比較しても結論が出なかった。HTML に書き出して、現状の図と並べた瞬間に Plan D の優位が一目で見えた",[183,306,307,310],{},[30,308,309],{},"マージのコンフリクト解決で revert が一部残ると、Worker bundle が予想外に膨らむ",": 削除したはずの vue ファイルが復活していないか、ビルド後に chunk リストを実測するのが安全",[183,312,313,316],{},[30,314,315],{},"「ビルドが通った」と「画面が出ている」は別の確認項目",": 構造判定だけで OK 報告して、TOC regression を後出しで発見した。dev サーバー + curl + スクショで毎節目に通すのを習慣化したい",{"title":318,"searchDepth":319,"depth":319,"links":320},"",2,[321,322,323,330,331,332,333,334],{"id":12,"depth":319,"text":12},{"id":36,"depth":319,"text":37},{"id":72,"depth":319,"text":73,"children":324},[325,327,328,329],{"id":84,"depth":326,"text":85},3,{"id":94,"depth":326,"text":95},{"id":101,"depth":326,"text":102},{"id":108,"depth":326,"text":109},{"id":137,"depth":319,"text":138},{"id":148,"depth":319,"text":149},{"id":174,"depth":319,"text":175},{"id":238,"depth":319,"text":239},{"id":282,"depth":319,"text":282},"dev","Nuxt 4 アプリの SSR Worker bundle が gzip 後 3.18 MiB まで膨らんで Cloudflare Pages の制限を超過した。プラン A/B/C/D を構成図に起こして比較し、最終的に HTML を public/ に直配置する Plan D へ収束させ、Phase 1 を本番デプロイするまでの設計判断と試行錯誤の記録。","md",{},true,null,"/cloudflare-pages-workers-bundle-architecture","eurekapu-nuxt4",false,"2026-06-16T00:00:00.000Z",{"title":5,"description":336},"2026-06/2026-06-16/cloudflare-pages-workers-bundle-architecture",[348,349,350,351,352,353],"Cloudflare Pages","Cloudflare Workers","Nuxt","アーキテクチャ","SSG","SSR","-VHNj0PVF33riZVt1fjaW9XKA6cfUv89EZqWboeRqMs",[],"https://log.eurekapu.com/og/blog/cloudflare-pages-workers-bundle-architecture.png?v=2026-06-16T00%3A00%3A00.000Z&title=Cloudflare%20Pages%20%E3%81%AE%201MB%20Worker%20bundle%20%E5%88%B6%E9%99%90%E3%81%AB%E5%BD%93%E3%81%9F%E3%81%A3%E3%81%A6%E6%A7%8B%E6%88%90%E5%9B%B3A%E3%80%9CD%E3%82%92%E6%9B%B8%E3%81%8D%E3%81%AA%E3%81%8C%E3%82%89%E8%A8%AD%E8%A8%88%E5%88%A4%E6%96%AD%E3%82%92%E6%95%B4%E7%90%86%E3%81%97%E3%81%9F&author=Kei%20Komatsu&sig=4f1f5e4eb78e7928",1782176328699]