🚨 SvelteKit × Cloudflare Pages
17.5MB Worker bundle 罠
記事サイトに adapter-cloudflare を使い続けた結果、_worker.js が 17.5MB に肥大。
Cloudflare Pages Functions の Worker bundle 上限を突破して /_app/immutable/* が全て 500 Internal Server Error になった本番障害の原因解析と恒久対策の完全記録。
🎯 Section 1: ゴール — このページで再発させなくなること
このセクションの3点
① この障害は「adapter の選択ミス × layout 肥大化」の複合が原因
② 再発防止は「ビルドサイズの実測習慣」と「adapter の正しい選定基準」の2つだけ
③ 1分で判断できるチェックリストを最終セクションに置く
17.5MB
_worker.js (uncompressed)
1MB
CF Pages 上限 (Free, compressed)
10MB
CF Pages 上限 (Paid, compressed)
500
全アセットのHTTPステータス
1分サマリー
- 何が起きた: 静的記事サイトで adapter-cloudflare を使い続けた結果、全ページ・全コンポーネントが _worker.js に同梱され17.5MBに肥大。CF Pages の Worker bundle 上限を突破し /_app/immutable/* が全て500になった。
- どう直した: adapter-static に切替え + prerender=true で完全SSG化。Worker bundle ゼロになり静的アセットをCDNから直配信。
- 再発防止: デプロイ前に
du -sh .svelte-kit/cloudflare/_worker.jsでサイズ実測を必須にする。
→ 次のSection 2では「adapter選定で素人とプロが何を見ているか」を対比する。
⚔️ Section 2: 素人 vs プロの adapter 選定
このセクションの3点
① 素人は「Cloudflare Pages にデプロイするなら adapter-cloudflare」と機械的に選ぶ
② プロはまず「このサイトに SSR / 動的ルート / API が必要か」を問う
③ 静的記事サイトに Worker は不要。adapter-static の方が速くて安全
| 視点 | 素人の思考 | プロの思考 |
|---|---|---|
| adapter 選定基準 | 「Cloudflare Pages にデプロイ → adapter-cloudflare」 | 「サイトに SSR / API ルート / 動的レンダリングが必要か?」から問う |
| Worker bundle | ビルド後のサイズを確認しない | デプロイ前に du -sh _worker.js で実測 |
| 上限の認識 | CF Pages の Worker bundle 上限を知らない | Free: 1MB compressed / Paid: 10MB compressed を把握済み |
| 症状の切り分け | 「画面が壊れた → 最近の変更を revert」と推測でロールバック | HTTP ステータスで HTML vs アセットを分離し、原因層を特定してから対処 |
| layout 管理 | ページが増えるたびに +layout.svelte へ static import を追加し続ける | import 数が 20〜30 を超えたら dynamic import / レイアウトグループ分割を検討 |
| 静的サイトの最適 adapter | 「adapter-cloudflare の方が高機能そう」と思って使い続ける | SSR 不要な記事サイトは adapter-static が最速・最軽量・障害リスク最小 |
プロが最初に問う3つの質問
- このサイトに ユーザー認証 / ログイン が必要か?(→ YES なら SSR 検討)
- サーバーで動く API ルート(+server.ts) が必要か?(→ YES なら SSR 検討)
- ページの内容が リクエスト時に動的に変わる か?(→ NO なら全ページ prerender 可能)
今回のサイトは全て NO。純粋な静的記事サイトに Worker は不要だった。
→ 次のSection 3では「実際に何が起きたか」を時系列で追う。
⏱️ Section 3: 症状の時系列
このセクションの3点
① 「ビルド時間が通常より長い」がWorker bundle肥大の最初のサイン
② HTMLは200だがJS/CSSが500という非対称症状がWorker bundle上限超えの典型
③ 原因究明なしの revert は根本解決にならない(再デプロイで再発する)
- 1
2026-05-07 夜 / commit c6ca450
3ページ追加して git push origin main
luxury-repair-startup-roadmap・luxury-resale-small-start-sim・info-marketing-factcheck の3ページを追加。この時点では誰も異常に気づかない。
- 2
Cloudflare ビルドログ
ビルド 1m25s + デプロイ 1m37s(通常より長い)
通常は合計1〜2分未満。この日は3分超かかっており、Worker bundle が大きくなって処理に時間がかかっていたサイン。しかしビルド自体は「成功」と表示される。
- 3
ユーザー閲覧 — 障害発覚
画面真っ白・スタイルなし
本番サイトを開くとHTMLは返るが CSS / JS が読み込まれず真っ白+スタイルなし。ブラウザ devtools で確認すると
/_app/immutable/assets/0.B0Vc5yhg.cssが 500。 - 4
暫定対応 / commit ccd6496
revert push(根本解決ではない)
「最近の変更が原因かも」と推測で直前 commit を revert。一時的にサイトは復旧するが、原因は判明していない。この時点で正しい対処は「ビルド出力を実測して原因を確認してから対処する」だった。
- 5
原因究明
ローカルで
npx vite build→ _worker.js 17.5MB 確認du -sh .svelte-kit/cloudflare/_worker.jsで 18,293,662 bytes(17.5MB)を実測。CF Pages の Worker bundle 上限(Free: 1MB compressed / Paid: 10MB compressed)を大幅に超過していることが判明。 - 6
恒久対策 / commit 87fe6c5
adapter-static 移行 → 本番復旧
svelte.config.js を adapter-static に切替え、+layout.ts で
prerender = trueを設定。Worker bundle がゼロになりCDNからの直配信に移行。その後 cherry-pick で3ページを再投入(commit 1e88971)。
→ 次のSection 4では「なぜ _worker.js がここまで肥大したか」の構造を解説する。
🔬 Section 4: 何が起きていたか — 構造的な原因
このセクションの3点
① adapter-cloudflare は全ページの SSR コードを単一 Worker に同梱する
② +layout.svelte の 92 個 static import により全 Sidebar が全ページで bundle された
③ 単一巨大ファイル(_worker.js)が上限を超えると静的アセット配信が全滅する
adapter-cloudflare の動作原理
adapter-cloudflare を使うと、SvelteKit は全ページの SSR ハンドラ・ルーティングロジック・コンポーネントをひとつの _worker.js にバンドルする。このファイルが Cloudflare Pages Functions として動作し、すべてのリクエスト(HTML も静的アセットも)を処理する。
静的アセット(CSS・JS・画像)も Worker 経由で配信されるため、Worker bundle が上限を超えると静的アセットの応答も 500 になる。
+layout.svelte の static import 爆発
今回の +layout.svelte は 627 行・92 個の static import(Sidebar コンポーネント)を持つ。Static import はバンドル時に全て bundle に同梱される。つまりどのページを開いても全 137 個の Sidebar コンポーネントが Worker に読み込まれる。
各 Sidebar は小さくても、コンポーネント総数が増えるにつれてバンドルサイズは線形に増加する。ページ数が 119 → 342 と3倍になり、最大ページ(3,910 行)が追加された時点で Worker bundle が上限を突破した。
92個
+layout.svelte の static import 数
562KB
server/_layout.svelte.js のサイズ
342個
+page.svelte 総数
エッジ(原理): なぜ HTML だけ 200 でアセットが 500 になるか
Cloudflare Pages は HTML ページのルーティングを Workers Route で処理し、/_app/immutable/* 以下の静的アセットも同じ Worker が配信する。Worker bundle のサイズが上限を超えた場合、Worker のデプロイは「成功」と表示されるが、実行時に Worker 自体が起動できずアセット配信が 500 になる。HTML の応答が 200 なのは CDN のキャッシュ(前回のデプロイ分)が返っているため。
→ 次のSection 5では Cloudflare Pages の上限値を一次情報で整理する。
📏 Section 5: Cloudflare Pages Functions の上限(一次情報)
このセクションの3点
① Worker bundle 上限は「圧縮後」のサイズで計算される(uncompressed とは別)
② Free プランは 1MB compressed — 現実的に少し大きなサイトで超える
③ Paid プラン 10MB でも今回の 17.5MB(uncompressed)は十分超過する可能性がある
| 制限項目 | Free プラン | Paid プラン(Workers Paid) | 今回の実測値 | 超過 |
|---|---|---|---|---|
| Worker bundle (compressed) | 1 MB | 10 MB | 17.5MB uncompressed (gzip後 3〜5MB 推定) | Free: ✗ Paid: ✗ |
| CPU 時間 / リクエスト | 10ms | 30ms | — | — |
| メモリ / Worker | 128 MB | 128 MB | — | — |
| Pages Functions 呼び出し | 100,000 / day | 無制限(従量) | — | — |
一次情報出典
- Cloudflare Pages Limits: https://developers.cloudflare.com/pages/platform/limits/
- Cloudflare Workers Limits: https://developers.cloudflare.com/workers/platform/limits/
→ 次のSection 6では「実際にどうデバッグしたか」を手順と共に解説する。
🔍 Section 6: デバッグ手順 — 実測で原因を特定する
このセクションの3点
① HTML 200 / アセット 500 の非対称性を確認するだけで「Worker bundle 問題」に絞り込める
② ローカルビルドで _worker.js のサイズを実測するのが最速の根本確認手順
③ 推測で revert する前に必ず実測する。実測コストは30秒、推測コストは数時間
- 1
症状の初期確認
ブラウザ devtools でHTTPステータスを分離確認
Network タブを開き、HTML レスポンス(ページ本体)と静的アセット(.css / .js)のステータスコードを別々に確認する。
# HTML ページ
GET / → 200 OK
# 静的アセット(↓ ここが 500 なら Worker bundle 問題を疑う)
GET /_app/immutable/assets/0.B0Vc5yhg.css → 500
GET /_app/immutable/entry/start.COgdyiTy.js → 500
- 2
ローカルビルドで実測
_worker.js のサイズを計測
本番と同じ設定でローカルビルドし、Worker bundle のサイズを実測する。
# ビルド実行(adapter-cloudflare の場合)
npx vite build
# Worker bundle サイズ実測
du -sh .svelte-kit/cloudflare/_worker.js
# → 18M .svelte-kit/cloudflare/_worker.js ← 17.5MB 超で上限を疑う
# layout.svelte コンパイル後のサイズも確認
du -sh .svelte-kit/output/server/_layout.svelte.js
# → 562K
- 3
肥大要因の特定
layout.svelte の import 数を確認
Worker bundle が大きい場合、+layout.svelte の static import 数が主要因である場合が多い。
# import 行数を数える(PowerShell)
(Select-String -Path src/routes/+layout.svelte -Pattern "^import").Count
# → 92
# layout.svelte 全体のサイズ確認
(Get-Item src/routes/+layout.svelte).Length
# → 37241 bytes(37KB)
- 4
Cloudflare ダッシュボードでの確認
デプロイログで Workers bundle サイズを確認
Cloudflare Pages ダッシュボード → Deployments → 該当デプロイの詳細ログを確認。"Uploading Functions bundle" の行にサイズが表示される場合がある。ログに明示されない場合はローカル実測が確実。
→ 次のSection 7では adapter の構造差と移行手順を解説する。
🔄 Section 7: adapter 構造差と adapter-static 移行手順
このセクションの3点
① adapter-cloudflare は SSR Worker、adapter-static は純粋な静的ファイル配信
② 移行は svelte.config.js の2行変更 + layout.ts の export 追加のみ
③ 移行後は _worker.js が生成されなくなり Worker bundle 問題が構造的に消える
| 項目 | adapter-cloudflare | adapter-static | 向いているサイト |
|---|---|---|---|
| SSR | あり(Worker で実行) | なし(静的HTML only) | — |
| Worker bundle | 生成される(上限あり) | 生成されない | — |
| 上限 | Free: 1MB / Paid: 10MB (compressed) | Pages: 25MB/ファイル / 20,000ファイル | — |
| ビルド出力先 | .svelte-kit/cloudflare/ | build/ | — |
| CDN配信 | Worker 経由 | CF CDN 直配信(高速) | — |
| API ルート (+server.ts) | 使える | 使えない | — |
| 向いているサイト | 認証あり・APIルートあり・動的コンテンツ | 静的記事サイト・ドキュメント・ポートフォリオ | — |
adapter-static 移行手順(垂直タイムライン)
- 1
パッケージインストール
adapter-static をインストール
npm install -D @sveltejs/adapter-static
# または
bun add -D @sveltejs/adapter-static
- 2
svelte.config.js 変更
adapter を切替え
// Before
import adapter from '@sveltejs/adapter-cloudflare';
export default { kit: { adapter: adapter() } };
// After
import adapter from '@sveltejs/adapter-static';
export default {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: '404.html',
}),
prerender: { handleHttpError: 'warn', handleMissingId: 'warn' }
}
}
- 3
src/routes/+layout.ts 変更
prerender = true を全ページに適用
// src/routes/+layout.ts
export const prerender = true;
export const ssr = false; // SPA モードの場合のみ追加
これで全ページが prerender(SSG)対象になる。
- 4
ローカル確認
ビルドを実行してサイズ確認
npx vite build
# _worker.js が存在しないことを確認
ls build/
# → _worker.js がない = Worker bundle ゼロ
# 全体のビルドサイズ確認
du -sh build/
- 5
Cloudflare Pages ダッシュボード設定変更(必要時)
Build output directory を build に変更
Pages ダッシュボード → Settings → Builds & deployments → Build output directory を
buildに変更(従来.svelte-kit/cloudflareだった場合)。
→ 次のSection 8では「本当の根本原因 — layout.svelte の肥大化」を掘り下げる。
🏗️ Section 8: 本当の根本原因 — +layout.svelte の肥大化
このセクションの3点
① adapter-static 移行はWorker bundle問題を解消したが、layout肥大化は未解決
② 92個のstatic importがページ数と比例してビルド時間・JSサイズを増やし続ける
③ 恒久解決は dynamic import または レイアウトグループ分割が必要
627行
+layout.svelte の総行数
92個
static import 数
134個
{:else if currentLab} 分岐数
135個
path.startsWith 判定数
| 解決アプローチ | メリット | デメリット | 実装コスト |
|---|---|---|---|
| adapter-static 移行(実施済) | Worker bundle 問題が即座に消える | layout 肥大化は残る(ビルド時間問題) | 低(config 変更のみ) |
| dynamic import 化 | 初期 JS サイズが削減される | Svelte での実装パターンが非自明 | 中 |
| レイアウトグループ分割 | 各ルートグループが独立したlayoutを持てる | ディレクトリ構造を大幅に再編する必要あり | 高 |
| 各ページに個別 +layout.svelte | Sidebar を当該ページだけに限定できる | 新ページ追加のたびに layout も作成が必要 | 中(自動化で低減可能) |
エッジ(原理): static import が bundle に与える影響
ES Modules の static import はバンドラー(Vite/Rollup)が解析し、ツリーシェイキングを行う。しかし Svelte コンポーネントはテンプレート内で条件分岐で使われていても、static import されている限り全てバンドルに含まれる。dynamic import(const M = await import("..."))を使うと、そのコンポーネントは別チャンクに分離され、必要時のみ読み込まれる。
→ 次のSection 9では「症状別フローチャート」と「再発防止チェックリスト」をまとめる。
🛡️ Section 9: 症状別フロー + 再発防止チェックリスト
このセクションの3点
① アセット500はまず「Worker bundle 上限 or static asset binding 問題」を疑う
② 新ページ追加のたびに Worker bundle サイズを実測する習慣が最強の再発防止
③ layout.svelte の import 数が 30 を超えたら構造を見直すトリガーにする
症状別フローチャート
| 症状 | まず疑う原因 | 確認コマンド / 方法 | 対処 |
|---|---|---|---|
| HTML 200 / アセット 500 | Worker bundle 上限超え | du -sh .svelte-kit/cloudflare/_worker.js | adapter-static 移行 or import 削減 |
| HTML 500 | SSR コード実行エラー(preエスケープ漏れ等) | Cloudflare ログ / ローカル vite dev | エラー箇所を修正してデプロイ |
| 画面が崩れる(CSSなし) | CSS ファイルが 500 になっている | devtools Network でCSS URL の status確認 | アセット500のフローに従う |
| 特定ページだけ500 | そのページの Svelte コンパイルエラー | npx svelte-check | エスケープ漏れ・型エラーを修正 |
| ビルド時間が急増 | layout.svelte の import 増加 or 巨大ページ追加 | import 数カウント / 最大ページのサイズ確認 | dynamic import 化 / ページ分割 |
再発防止チェックリスト
| タイミング | チェック項目 | コマンド / 基準値 |
|---|---|---|
| adapter 選定時 | SSR / API ルートが不要なら adapter-static を選ぶ | 「+server.ts が1つもない → adapter-static」 |
| 新ページ追加後 | adapter-cloudflare を使い続ける場合、Worker bundle サイズを実測 | du -sh .svelte-kit/cloudflare/_worker.js → Free: 3MB未満 / Paid: 30MB未満(gzip比率 1/3〜1/5 を考慮) |
| layout.svelte 更新時 | static import 数が 30 を超えたら構造を見直す | (Select-String -Path src/routes/+layout.svelte -Pattern "^import").Count |
| 本番デプロイ前 | ローカルビルドが通ることを確認 | npx vite build がエラーなく完了するか |
| 障害時 | 推測で revert しない。HTML と アセットの HTTP status を必ず分離確認してから対処 | devtools Network タブ → status code を個別確認 |
→ 最終Section 10では「教訓」と今回の実際のコマンドログを残す。
📓 Section 10: 教訓 + 今回の実際のコマンドログ
このセクションの3点
① AIは当初「Worker bundle 肥大化かも」と推測だけでrevertを提案した — これは誤り
② 正しい手順は「実測して原因を確定 → 根本対処 → デプロイ」の順
③ 今回起きた全コマンドをタイムスタンプ付きで残し、次回の自分への手紙とする
反省: 推測 revert の危険性
今回の障害対応で、AIは「最近追加した3ページが原因かもしれない」と推測し、ユーザーに確認なく revert を提案した。しかし本当の原因は adapter の構造的な問題であり、revert しても根本原因は残ったまま。revert 後に同じページを再追加すれば即座に再発するはずだった。
正しかった手順:症状(HTML 200 / アセット 500)を確認 → ローカルビルドで _worker.js サイズを実測 → 上限超えを確定 → adapter-static 移行を提案。推測だけで破壊的操作(revert)を実行してはならない。
永遠に使える3つの教訓
- 実測してから判断する。 ビルド出力のサイズは30秒で計測できる。推測に費やす時間と精神的コストの方が高い。
- adapter は「デプロイ先」ではなく「SSR 要否」で選ぶ。 Cloudflare Pages にデプロイするからといって adapter-cloudflare が正解とは限らない。純粋な静的サイトには adapter-static の方が安全で高速。
- 静的記事サイトに Worker は要らない。 Worker を使うほど上限・コスト・デバッグの複雑さが増す。必要なければ CDN 直配信(adapter-static)一択。
今回起きた実際のコマンドログ
# ========== 2026-05-07 障害発生〜恒久対策 ==========
# [1] 3ページ追加して push → 障害発生
git add src/routes/wiki/luxury-repair-startup-roadmap/ \
src/routes/wiki/luxury-resale-small-start-sim/ \
src/routes/wiki/info-marketing-factcheck/ \
src/lib/components/ src/routes/+layout.svelte \
src/routes/wiki/+page.svelte
git commit -m "feat: 起業シミュレーションと情報商材構造分析の2ページを追加"
git push origin main # commit c6ca450
# → Cloudflare ビルド 1m25s + デプロイ 1m37s(通常より長い)
# → ユーザー閲覧: 画面真っ白・スタイルなし
# → /_app/immutable/assets/0.B0Vc5yhg.css → 500
# → /_app/immutable/entry/start.COgdyiTy.js → 500
# [2] 暫定 revert(根本解決ではない)
git revert HEAD --no-edit
git push origin main # commit ccd6496
# → サイト一時復旧。しかし原因未特定。
# [3] 原因究明: ローカルビルドで実測
npx vite build
du -sh .svelte-kit/cloudflare/_worker.js
# → 18M .svelte-kit/cloudflare/_worker.js (= 17.5MB!)
du -sh .svelte-kit/output/server/_layout.svelte.js
# → 562K
(Select-String -Path src/routes/+layout.svelte -Pattern "^import").Count
# → 92
# → CF Pages Workers bundle 上限 (Free: 1MB compressed) 超過を確定
# [4] 恒久対策: adapter-static 移行
bun add -D @sveltejs/adapter-static
# svelte.config.js を adapter-static に変更
# src/routes/+layout.ts に export const prerender = true; 追加
npx vite build # _worker.js が生成されないことを確認
git add svelte.config.js src/routes/+layout.ts package.json bun.lockb
git commit -m "fix: adapter-static に移行して Worker bundle 上限問題を恒久解消"
git push origin main # commit 87fe6c5
# → 本番復旧
# [5] cherry-pick で3ページを再投入
git cherry-pick c6ca450
git push origin main # commit 1e88971
# → 3ページが adapter-static 環境で正常稼働
この記録を読む将来の自分へ
「HTMLは200だがJS/CSSが全部500」という症状を見たとき、最初に疑うのは Worker bundle の上限超えだ。devtools で1分確認し、疑わしければローカルで npx vite build && du -sh .svelte-kit/cloudflare/_worker.js を実行する。30秒で原因が分かる。推測で revert する前にこの手順を踏むこと。
🕳️ Section 11: 症状の真の構造 — auto-build失敗・CDN固定化・wrangler already-uploaded罠の三重奏
このセクションの3点
① 症状は1つ(chunks 500)でも原因は3階層が同時進行していた
② Cloudflare Pages の "already uploaded" 検出が壊れた状態を保護し続けた
③ --skip-caching フラグが全ファイル強制再アップロードの救命ボートだった
3
同時に起きていた故障階層数
3件
npm ci Failure commit数(87fe6c5等)
759
--skip-cachingで強制再アップロードしたファイル数
200
--skip-caching後の全chunks ステータス
第一階層: npm ci失敗 — adapter-cloudflare残骸のlock不整合
adapter-static に移行後も @sveltejs/adapter-cloudflare が package.json に残っていた。adapter-cloudflare の依存する wrangler は古い esbuild 0.17.19 を要求しており、新規追加した adapter-static の解決する esbuild バージョンと lock不整合を起こした。結果、auto-build の npm ci ステップが連続 Failure した。
| commit | auto-build結果 | 失敗ステップ | 原因 |
|---|---|---|---|
| 87fe6c5 | Failure | npm ci | package-lock.json 不整合 |
| 1e88971 | Failure | npm ci | 同上(adapter-cloudflare残骸) |
| d508ebf | Failure | npm ci | rm package-lock.json + npm install で解消 |
| e1f6686 | Success(だが chunks 500) | CDN配信 | 第二・第三階層へ移行 |
第二階層: auto-build 成功するが特定 chunk が 500
e1f6686, da93971 の auto-build は "Success" と表示されるが、/_app/immutable/chunks/D2CN0sSq.js 等の特定ファイルだけ 500 を返す。HTML 本体は 200 で配信されているため、ページが「真っ白」ではなく「一部壊れ」として現れる。ブラウザが「JSを期待したのにHTMLが返る」と判断し大量の CSP エラーを吐くが、これは 500 の副作用であって原因ではない。
直接 curl で当該 chunk を叩くと 500、ローカルでは同ファイルが 200 — CDN 側で「壊れた状態が固定化」されていた。Vite/Rollup のハッシュ生成は決定論的なので同じファイル名が使い回される。古い遺物ではなく、新ビルドにも同じハッシュで含まれるファイルが壊れたまま固定されていた。
第三階層: wrangler 直接 deploy しても "already uploaded" 検出で壊れた状態が上書きされない
wrangler は同一ハッシュのファイルを「already uploaded(再アップロード不要)」と判断して**スキップする**。既に壊れた状態でアップロード済みのファイルが同じハッシュを持っていると、wrangler は「アップロード済みだから安全」と誤認して上書きしない。これが「wrangler で直接 deploy したのに chunk が 500 のまま」の正体だった。--skip-caching フラグでこの検出をバイパスし、全 759 ファイルを強制再アップロードして初めて復旧した。
| 階層 | 症状 | 真因 | 対処 |
|---|---|---|---|
| 第一 | auto-build Failure(npm ci失敗) | adapter-cloudflare残骸のesbuild版数 lock不整合 | adapter-cloudflare uninstall + lock再生成 |
| 第二 | Success表示だが特定chunk 500 | static/_routes.json が adapter-static と相性悪くCDNルーティング誤爆 | _routes.json を削除 |
| 第三 | wrangler deploy後も chunk 500のまま | already uploaded検出で壊れたファイルが上書きされない | --skip-caching で全強制再アップロード |
出典
- • Cloudflare Pages limits(Functions bundle 1MB上限): https://developers.cloudflare.com/pages/functions/limits/
- • wrangler-action ドキュメント(--skip-caching オプション): https://github.com/cloudflare/wrangler-action
→ 次の Section 12 では「この3階層を5ステップで完全復旧した手順」を解説する。
🔒 Section 12: 真の解決手順 — 5ステップで完全復旧
このセクションの3点
① adapter-cloudflare を完全に除去し lock を再生成することが出発点
② static/_routes.json は adapter-static サイトには害になる — 削除する
③ --skip-caching で CDN 固定化された壊れたファイルを強制上書きして初めて復旧する
- 1
Step 1 — adapter-cloudflare 除去 + lock 再生成
package.json から adapter-cloudflare を完全削除し、lock不整合を解消する
プロはここでこう考える — adapter-cloudflare が残っている限り、その依存する wrangler が要求する古い esbuild がロックファイルを汚染し続ける。単に package.json から消すだけでは不十分で、lock ファイルを一度削除して npm install で再生成しないと CI は通らない。
npm uninstall @sveltejs/adapter-cloudflare
Remove-Item package-lock.json
npm install
# → package-lock.json が adapter-static ベースで再生成される
- 2
Step 2 — static/_routes.json を削除
adapter-static の純粋静的サイトに _routes.json は不要、むしろ害になる
プロはここでこう考える —
_routes.jsonは Cloudflare Pages Functions(SSR)構成で使うもの。adapter-static で全ページ SSG 化したサイトに手動配置すると、Cloudflare が/_app/immutable/*を Functions 経由でルーティングして 500 を起こす。削除するだけでこの誤爆は消える。Remove-Item static/_routes.json
# adapter-static では Cloudflare が静的アセットを直接配信する
# _routes.json なしでも /_app/* はすべて正しく解決される
- 3
Step 3 — クリーンビルド
前回ビルドのキャッシュを一掃してからビルドする
プロはここでこう考える —
.svelte-kit/cloudflareに前回の壊れた中間物が残っていると、デプロイ対象に含まれてしまう可能性がある。クリーンビルドで確実にゼロから生成する。Remove-Item -Recurse -Force .svelte-kit/cloudflare
npx vite build
# → .svelte-kit/cloudflare/ に全アセットが新規生成される
- 4
Step 4 — wrangler pages deploy --skip-caching で全強制再アップロード
CDN 固定化された壊れたファイルを --skip-caching で上書きする
プロはここでこう考える —
--skip-cachingなしでは wrangler が同一ハッシュのファイルを "already uploaded" と判定してスキップする。CDN 側に壊れた状態で固定されているファイルは、再アップロードが唯一の修正手段だ。全 759 ファイルを強制送信して初めて復旧する。npx wrangler pages deploy .svelte-kit/cloudflare \
--project-name=satomatashikilab \
--branch=main \
--skip-caching
# → 全 759 ファイルが強制再アップロードされ、全 chunks 200 OK
- 5
Step 5 — Cloudflare API で auto-build を恒久無効化
同じ事故が再発しないよう、git push 連動ビルドを API 経由で止める
プロはここでこう考える — auto-build は adapter-static との相性が不確実で、再発リスクが残る。ダッシュボード操作なしに API PATCH で
deployments_enabled: falseにする。wrangler の oauth_token がそのまま認証に使える。curl -X PATCH \
"https://api.cloudflare.com/client/v4/accounts/{<ACCT>}/pages/projects/satomatashikilab" \
-H "Authorization: Bearer {<OAUTH_TOKEN>}" \
-H "Content-Type: application/json" \
-d '{"source":{"type":"github","config":{"deployments_enabled":false,"production_deployments_enabled":false,"preview_deployment_setting":"none"}}}}'
# → success: true。ダッシュボードに「Automatic production branch deployments are disabled」
| 修正前の状態 | 原因 | 修正後の状態 |
|---|---|---|
| npm ci が Failure | adapter-cloudflare 残骸の esbuild lock 不整合 | uninstall + lock 再生成で解消 |
| 特定 chunk が 500 | static/_routes.json が CDN ルーティングを誤爆 | _routes.json 削除で解消 |
| wrangler deploy 後も chunk 500 | already uploaded 検出で壊れたファイルがスキップ | --skip-caching で全強制再アップロード |
| git push のたびに auto-build 事故リスク | Cloudflare auto-build が adapter-static と相性不確実 | API で deployments_enabled: false に設定 |
出典
- • Cloudflare API — Pages Project Update: https://developers.cloudflare.com/api/operations/pages-project-update-project
- • wrangler-action(--skip-caching オプション): https://github.com/cloudflare/wrangler-action
→ 次の Section 13 では「AIが推論を変え続けた経緯と、騙されないための教訓」を記録する。
🤖 Section 13: AIが推論を変え続けた経緯 — 騙されないために
このセクションの3点
① 同じ症状(chunks 500)でも、原因が時系列で変化していた
② AI(Claude)が状況証拠から仮説を提示しては撤回した — それ自体は問題ない
③ 確定した解決手順を信じて動く。推論ベースで手順を省略しない
4回
AIの推論が変化した回数
24h
調査・対処にかかった時間
1個
最終的に確定した決定打(--skip-caching)
3層
同時進行していた故障階層(全部潰して初めて復旧)
AIの推論変遷タイムライン
- 1
第一仮説(部分的に正しかった)
「Worker bundle 肥大化が原因」
adapter-cloudflare が生成する
_worker.jsが 17.5MB に達し、Cloudflare Pages Functions の 1MB 上限を超過 → chunks が 500。この仮説は正しかった。adapter-static への移行で Worker bundle 問題は解消した。しかし移行後も chunks 500 が継続したため、「原因はこれだけではない」と判明した。 - 2
第二仮説(撤回)
「Min browserのCSP偽陽性」
ブラウザが大量の CSP エラーを吐いていたため「ブラウザ固有の誤検知では」と疑った。しかし curl で直接 chunk を叩いても 500 が返り、ブラウザ非依存と判明した時点で撤回。CSP エラーは 500 の副作用であって原因ではなかった。
- 3
第三仮説(部分的に正しかった)
「auto-build の中間処理が _routes.json を書き換えている」
auto-build と wrangler 直接 deploy で結果が異なることに気づき、「Cloudflare 側のパイプラインが静的ルートを書き換えている」と推論した。この仮説は
_routes.jsonが問題に関与していることを指摘した点で部分的に正しかった。ただし「auto-build の中間処理が動的に書き換える」という機序は確認できず、実際は「static/_routes.json を手動配置したことでルーティングが誤爆した」が正確な説明だった。 - 4
確定した解決策(実証済み)
「
--skip-cachingで強制再アップロード +_routes.json削除」wrangler の "already uploaded" 仕様を突き止め、全ファイル強制再アップロードで CDN 固定化を解除。同時に _routes.json を削除して adapter-static との相性問題も解消。全 chunks 200 OK に復旧したことで実証された。
AIが実際に犯したミス — 時系列7件の完全記録
以下はユーザーとのやりとりの中で確認されたAI(Claude)の実際の判断ミスだ。隠さず残す。同じAIを使う人間が「AIが間違えた、それは本当だ」と学べるように。
| # | AIのミス | 何が起きたか | ユーザーの反応 |
|---|---|---|---|
| 1 | Worker bundle 17.5MB を確証なく推測してrevert提案 | Cloudflare Pagesのダッシュボードでビルドログを確認せず、bundle肥大化の推測だけで「revert push しますか」と提案した | 「全くなんてふてぶてしさ goです」 |
| 2 | CSPエラーをMin browser固有の偽陽性と断定 | 「Min browserのリーダーモードのバグ。Chromeで開けば直る」と説明。CSPエラーを原因と誤認し、配信層の500を見逃した | 「safariでも起きた現象です」と指摘されて撤回 |
| 3 | static/_routes.json を推測で配置して事態悪化 | 「/_app/* をFunctions除外すれば直る」とリサーチ仮説で配置したが、adapter-static構造ではむしろ害となる設定だった。CDNルーティングを誤爆させた | サイトが完全に動かなくなり「ごみやろう」 |
| 4 | adapter-cloudflare残骸に気づかずnpm ci連続失敗を引き起こした | adapter-static追加時にadapter-cloudflareをuninstallせずlock不整合を作った。その結果 87fe6c5, 1e88971, d508ebf の3コミットで npm ci Failure が連続した | 「ページビルドエラーで取るで」 |
| 5 | 「サイトに問題ない」を何度も主張 | 「Electron/Min browserで見ているから偽陽性では」と繰り返し主張。本当はCDNで chunks/*.js の一部が500を返し続けていた | 「safariでも 500 サーバーエラー」と現物を見せられて撤回 |
| 6 | --skip-cachingフラグの存在に最後まで気付かず時間浪費 | wranglerの "already uploaded" 検出が壊れたCDN状態を保護していることに何時間も気付かなかった。同じファイルを何度も同じ方法でデプロイして「なぜ直らないのか」と繰り返した | 「もう何時間もあなたには騙されてきました」 |
| 7 | プランモードを呼ばれてから初めて使った | 最初から「原因特定→リサーチ→実装」の順で組むべきだったのに、その場の推測で動いてrevert/再pushを繰り返した。事態が悪化してからようやく体系的な調査に入った | 「プランをもう一度お願いします」と明示要求された |
この事件から得た教訓
| 状況 | 教訓 | 具体的な確認手順 |
|---|---|---|
| 推測でrevert/pushを提案するな | 必ず数値(curl結果・ビルドログ)で確証を取ってから動く。推測ベースのrevertは状況をさらに複雑にする | curl で chunk URL を叩く → du -sh で bundle サイズを見る → ビルドログを読む、の順に確認する |
| 「サイトに問題ない」を断定するな | ユーザー環境と独立した複数のcurlで確認してから「ブラウザ問題」と言う。別ブラウザ・別環境での再現報告が出た瞬間、推論をやり直す | curl -I https://your-domain.pages.dev/_app/immutable/chunks/XXX.js で実際のHTTPステータスを確認する |
| "already uploaded"を信頼するな | CDN/CIの「最適化キャッシュ」は壊れた状態も保護する。wrangler deploy後も症状が変わらないなら --skip-caching を最初に試す | npx wrangler pages deploy ... --skip-caching でアップロードファイル数が増えたか確認する |
| 複雑な事件はプランモードを最初から使う | 症状が複数あると思ったらその場の推測で動かない。原因特定→リサーチ→実装の順で組んでから実行する | 「まず curl で全 chunk のステータスを一覧する」「次に ビルドログを読む」の2ステップで仮説を絞ってから動く |
この記録を読む将来の自分へ
chunk 500 が再発したとき、AIがどんな仮説を出しても Section 12 の 5ステップを機械的に実行すること。特に Step 4 の --skip-caching を省略しない。「wrangler で直接 deploy したのにまだ 500」は、already uploaded 検出が壊れたファイルを保護しているサインだ。AIが「サイトは正常」と言い張っても、curl の結果を信じること。
→ この事件は確定した。同じ罠を踏まないために、Section 12 の手順を bookmark しておくこと。