起こったこと
朝、昨日のデプロイ失敗ログを開き直したところから始まった。prerender が全件 500 で落ちていた現象は、auth.server.ts プラグインが prerender 時にも走って、better-auth が D1 にアクセスしようとして例外を投げていたのが犯人だった。fetchSession() を prerender 中はスキップするように直したら、51 routes すべて 200 で返るようになった。
ところが次に Worker bundle のサイズでぶつかった。非圧縮 16 MiB、gzip 後 3.18 MiB。Cloudflare Pages の Worker bundle は無料で 1 MiB、有料 ($5/月) で 10 MiB が上限と Codex に確認したら、思った以上に天井が低いとわかった。「5ドル払ったとして 10MiB にしか増えない」と気づいた瞬間、当初の方針を捨てて構成自体を見直す方向に切り替わった。
SSR で粘ったがダメだった経路
最初に試したのは routeRules で /takken/** を ssr: false にする案。i18n の routeRules 書き換えで glob が効かなかったので、51 件の具体パスに展開してリトライ。それでも Worker bundle は 3.175 MiB のまま落ちなかった。
その次に各ページの definePageMeta に ssr: false を sed で一括注入した。これも効かなかった。Codex に状況を投げて返ってきた結論は明確で、Nuxt の ssr: false はリクエスト時 SSR をスキップするだけで、ビルド時に Worker bundle からコードを除外しない。58 個別チャンク (非圧縮 ≒ 1 MB、圧縮後 ≒ 170 KB) は SSR をやめても bundle に焼き込まれ続ける。「ビルド時に Worker から完全除外する公式手段は無い」と書かれていて、SSR で粘る発想を一度全部捨てる必要があった。
プラン A〜D を構成図に起こした
ここで頭の中の見取り図が破綻したので、HTML メモを切って構成図を 4 枚描いた。memo/2026-06-16/cloudflare-architecture-options.html に保存し、ローカルサーバーを立てて行ったり来たりした。
Plan A: takken だけ別 Nuxt サブビルドで静的化
/takken/ 配下を独立した Nuxt プロジェクトとしてビルドし、Cloudflare Pages の別プロジェクトに割り当て、本体側はリバプロでルーティングする案。Worker bundle は確実に分離できるが、認証セッションを別ドメイン (もしくは別 Worker) と共有する仕組みを別途設計する必要があり、構成の複雑さが一段増える。
Plan B: takken を SPA 化して CDN に投げる
Plan B は最初から候補にしていたが、認証付きのページを SPA で配ると初回表示で「白画面 + ローディング → セッション判定 → 出し分け」のフローが必須になり、SEO もユーザー体験も劣化する。ユーザーから「Plan B はありえない」と即却下された。
Plan C: フロントとバックエンドを別リポに割る
Codex 調査でも触れた構成で、よく「CGM 系のサイト (note.com 等) はこうしている」と説明される形。フロントは静的サイト生成器で吐き、バックエンドは API リポジトリとして独立させる。長期的には強いが、今のリポを縦に切り直すコストが大きく、認証フローも一度全部書き直す。今日この場で着地させるには手が遠い。
Plan D: HTML/JS 直配信、SSG メインで対象オブジェクトはオブジェクトストレージへ
途中で「いや、/takken/ の本文はそもそも Vue ファイルである必要があるのか?」という問いが出てきた。HTML として吐いたものを public/content/takken/{slug}.html のように置き、動的ルートが fetch して v-html で流し込めば、Worker bundle にコードが焼き込まれない。これが Plan D の核だった。後で本数が増えたら R2 などのオブジェクトストレージに逃がす道も自然に開く。
ここで「マークダウンにすれば自動で R2 に行く」と誤解しかけたが、マークダウン化と R2 移行は別の話だ。HTML 化は「Worker bundle から本文を抜く」目的、R2 移行は「public/ が肥大したら逃がす」目的。HTML メモにも「マークダウン化 ≠ R2 移行 (よくある誤解)」セクションを切って明示した。
「Vue ファイルでもいいじゃん」がダメな理由
途中で「PageView の Vue ファイルでもいいんじゃない?」という方向に揺り戻しがあった。これは今日の 3 MiB 超過の元凶そのもので、Vue ファイルとして書くと そのページが import した API client・auth client・ストアまで芋づる式に Worker bundle に焼き込まれる。本文だけ HTML としてビルド時に切り出し、Vue 側は薄い動的ルートに留めれば、bundle に乗るのは「動的ルート 1 本ぶん」で済む。Nuxt と Vue を捨てるわけではなく、共通レイアウト・ヘッダ・フッタは layouts と composable で再利用する。本文だけ HTML で外出しする、という線引きに落ち着いた。
Phase 1 計画書を書いて Codex に 3 ラウンドかけた
Plan D で進めると決まったところで、計画書を plan-d-implementation-plan.html として書き起こした。Codex に投げたら 4 点指摘が返ってきて、Step 3-1 の対象ファイル指定 ([...slug].vue) を修正、期間表記の削除、Phase 1 を takken に絞る再構成、テスト戦略の追加、で v1.2 に直した。
ユーザーから「Phase 2/3 も書き切らないと Phase 1 の妥当性は評価できない」と指摘が入り、plan-d-phase2-implementation-plan.html (lessons 移行) と plan-d-phase3-implementation-plan.html (LP・固定ページ) も追加で書いた。書いてみると Phase 1 に先回りで組み込むべき調整事項が 5 つ洗い出され、特に「component transform registry」「Phase 3 LP は静的本文だけ外出し、動的部品は Vue 側」あたりは Phase 1 の API パスと composable の設計に直接効いた。Codex のレビューを 3 ラウンド回して「OK 致命点なし」を獲得した。
Phase 1 実装: Step 0〜4 のデプロイとロールバック
午後セッションで Step 0〜4 を一気通貫で実装した。
- Step 0: ブランチ切替、判定基準ドキュメントとテスト雛形を並列で作成
- Step 1.5 POC:
useArticleDomHookscomposable を抽出、useContentと page-content API を作成、変換スクリプトの骨格と動的ルートを実装、dairi.htmlと_meta.jsonを手作業で 1 件だけ作って判定 3 項目を実測。すべて OK - Step 2: 残り 48 件を変換スクリプトで一括変換。途中でテンプレリテラル展開漏れと
_meta.json上書きでエントリが消える事故があり、git restore して再変換。最終的に Worker bundle 2.909 MiB で 3 MiB 上限をクリア - Step 3: Vitest 全 2526 件 pass、Playwright は環境問題で 44 件 failed だったので curl の smoke test 49 件に切り替えて全 200 を確認
ここで節目ごとに止まり、ユーザーに承認を貰ってから次に進む段取りにしておいた。POC 完了時に「表示を確認していますか?」と突っ込まれ、ビルド成果物の構造判定だけで OK 報告していたことが露呈した。dev サーバーを起動して curl で叩き、ブラウザでスクショまで撮ったら、dairi で右サイド TOC が出ていない regression を発見した。v-html で挿入された article の h2/h3 を setupToc が拾えていなかったので MutationObserver を追加して直した。「ビルドが通った」と「画面が出ている」を分けて確認する大切さを、また 1 回叩き込まれた。
本番デプロイ → 404 → ロールバック → Codex 診断
scripts/deploy.ps1 を見落としていて、ユーザーに「これでしょ」と指摘されて思い出した。wrangler pages deploy --branch main で本番反映、4.8 分で https://eurekapu-nuxt4.pages.dev にデプロイ成功。
ところが本番で 49 件すべて 404 が返ってきた。API は 200 で返るのに /takken/* の SSR ルーティングだけ通らない。一度ロールバックを試みたが、ロールバック前の状態が元々 Worker サイズ超過で deploy できなかったので、当然ロールバックの deploy も失敗した。「ロールバックしてデプロイ成功するわけない、それが原因でやってるんだから」とユーザーに正面から指摘され、Codex に原因を深掘りさせる方向に切り替えた。指示書を memo に書き、codex exec で直接 CLI を叩いた。Codex から致命的原因が明確に返ってきて、修正を 2 ファイルに当てた後、wrangler pages dev で本番相当ランタイムで検証し、49 件すべて 200 を確認した。
ところが main にマージし直したら 48 件の vue chunk が復活して Worker サイズが再超過。ロールバックの revert が部分的に残っていて、削除したはずの vue ファイルが戻ってきていた。ここで時間切れ。引き継ぎ memo (memo/2026-06-16/plan-d-deploy-failure-handoff.md) を書いて commit し、deploy 失敗状態を明記したまま終了した。
学び
- Cloudflare Pages の Worker bundle 上限は思ったより低い: 無料 1 MiB、有料でも 10 MiB。「サーバーは贅沢に使える」前提だと早晩ぶつかる
- Nuxt の
ssr: falseは Worker bundle 削減には効かない: リクエスト時 SSR をスキップするだけ。bundle から物理的にコードを抜くには「Vue ファイルとして import される経路を断つ」必要がある - 構成図を描くと判断が早くなる: 頭の中だけで 4 案を比較しても結論が出なかった。HTML に書き出して、現状の図と並べた瞬間に Plan D の優位が一目で見えた
- マージのコンフリクト解決で revert が一部残ると、Worker bundle が予想外に膨らむ: 削除したはずの vue ファイルが復活していないか、ビルド後に chunk リストを実測するのが安全
- 「ビルドが通った」と「画面が出ている」は別の確認項目: 構造判定だけで OK 報告して、TOC regression を後出しで発見した。dev サーバー + curl + スクショで毎節目に通すのを習慣化したい