なぜ計測することにしたか
Cloudflare Pages へのデプロイが「最近なんか長い」という感覚だけで止まっていた。pnpm generate が 10 分以上回ることは知っているが、その 10 分のうち何分が prerender で、何分が postgenerate の検証スクリプトで、何分が wrangler deploy なのか、内訳を一度も手元に持っていなかった。
感覚で「Nuxt のビルドが遅い」と言い続けても、改善する場所が決まらない。フェーズ別の秒数が出れば、どこに手を入れれば一番効くか判断できる。記事数が 1000 本を超えたタイミングで、いちど計測スクリプトを通すことにした。
measure-deploy.ps1 でフェーズを切る
PowerShell でフェーズ別に時間を測るスクリプトを scripts/measure-deploy.ps1 に置いた。やっていることは単純で、
pnpm generateの開始・終了時刻を記録するwrangler pages deployの開始・終了時刻を記録する- 各フェーズの所要時間を秒で出す
筆者は PowerShell を書き慣れていないので、骨格は Claude Code に書かせて、出力フォーマットだけ自分の見たい順に並べ替えてもらった。
PS C:\Users\numbe\Git_repo\mdx-playground> powershell -ExecutionPolicy Bypass -File scripts/measure-deploy.ps1
=== Nuxt Build & Deploy Phase Timer ===
スクリプトを叩いた瞬間、ヘッダが出てビルドが走り始めた。あとはコンソールを眺めて、各フェーズの秒数が積み上がっていくのを待つだけ。
pnpm generate が exit 1 で落ちる
ところがビルドの終盤、コンソールに赤い文字が流れた。
pnpm generate failed (exit 1)
CategoryInfo : OperationStopped: (pnpm generate failed (exit 1):String) [], RuntimeException
exit 1 だけ見ると「ビルド本体がコケた」と読みたくなる。実際、最初は SSG のメモリ問題が再発したのかと身構えた。
ログを上に遡って読むと、ビルド本体(Nitro の prerender)は完走していた。落ちていたのは postgenerate フックで動かしている apps/web/scripts/verify-blog-payload.mjs だった。このスクリプトは「カレンダーに出るべき記事数」と「実際に出力された HTML に含まれる記事数」を突き合わせるチェックを担っていて、その差分検出で process.exit(1) を投げていた。
エラーメッセージは「2026-06 のカレンダーに 27 本あるはずなのに 24 本しか HTML に出ていない」という内容。3 本どこかへ消えた。
消えた 3 本を追う
カレンダーから抜けている 3 本の frontmatter を見にいった。原因は単純で、3 本のうち 2 本が unpublished: true、もう 1 本はビルドの途中で書き足したばかりで、検証スクリプトが見にいったタイミングではまだ HTML に焼き込まれていなかった。
検証スクリプトの collectArticleIndexFromContent 側は unpublished: true の記事も「期待値」に含めていた。期待値には入れているのに、SSG 側はそれを除外して出力する。当然差分が出る。
修正は 1 箇所で終わった。verify-blog-payload.mjs の期待値生成で、frontmatter に unpublished: true がある記事を除外するロジックを足す。同じリポジトリ内に verify-unpublished-excluded.mjs という別の検証スクリプトが既にあって、そちらは /^unpublished:\s*true\s*$/m で unpublished: true を判定していたので、正規表現の書き方もそちらに揃えた。
ここで pnpm generate をもう一度回すと検証だけで 10 分待たされる。先に検証ロジック単体を node で叩いて、正規表現が 3 ケース(unpublished: true / unpublished: false / フィールドなし)を正しく仕分けることだけ確かめた。フルビルドは流さなかった。
モデルを Opus 4.7 1M context に切り替えた
セッション後半、コンテキストが膨らんできた。frontmatter の Read、検証スクリプトの差分、ログの分析と、参照ファイルが増えていく。
ここで筆者が Claude Code に対して「Opus 4.7 (1M context) の指定方法を教えて」と聞いた。長いログを最後まで追いかけるなら 1M context モデルに切り替えたかったからだ。
最初は /model コマンドに完全な ID(claude-opus-4-7[1m])をそのまま渡して弾かれた。
❯ /model
⎿ Kept model as Opus 4.8 (1M context) (default)
エイリアスではなく表示名や角括弧入り完全 ID を投げたのが原因だった。引数なしで /model を叩くと選択 UI が出る。一覧から Opus 4.7 (1M context) を選ぶ方式が一番確実だった。これ以降のセッションは長文ログをそのまま読ませても余裕が残った。
ビルド時間の内訳を見て気づいたこと
計測スクリプトが吐いた数字を、過去のビルドログと並べた。結論は予想外だった。
- 今回のルート数は 2724(普段より少ない)
- それなのにルートあたりの prerender 時間が普段の倍
ルート数が減って総時間も短くなる、なら素直な話で済んだ。ルート数が減っているのに 1 ルートあたりの時間が伸びている、というのは別の話で、特定のページが詰まっている可能性が高い。
調査メモを memo/2026-06-04/ にマークダウンで残した。「どのページが詰まっているか」をスローページ単位で特定するのは翌日の作業として積み残し、今日はここまでで切ることにした。
ブラウザ側の確認でハイドレーション崩れも見つかる
ビルドの数字を見たあと、デプロイ済みの log.eurekapu.com/blog を agent-browser で開いて表示確認した。すると別の症状が出ていた。
- サーバが返す HTML はちゃんと 2026 年 6 月で 24 件入っている
- ブラウザに見えているカレンダーは 2026 年 5 月のまま
HTML と DOM が食い違っている。クライアント側の payload が古いビルドのものを参照していて、ハイドレーション後に 6 月が消えていた。ビルド ID と payload のキャッシュがずれていた。
ビルド時間の計測から始まったセッションで、思いがけず「デプロイ後の表示崩れ」まで掘り当てた。一日のうちに 2 つ別の問題が見えたが、今日のスコープはビルド時間の計測まで、と決めて切った。
ドキュメント+Codex レビューで締める
ここで筆者は「もう今日は帰りたいんで、ドキュメントをして、OpenAI の codex のレビューを受けてください」と Claude Code に投げた。
Claude Code が memo/2026-06-04/ の調査メモをまとめ直し、そのまま /codex-review-doc を叩いてドキュメントを Codex(GPT-5 系)に投げた。途中で Codex の config.toml にエラーがあって設定を直す手間が入ったが、本筋ではないので省く。
Codex から致命的な指摘が 3 つ返ってきた。瑣末な指摘ではなく、どれも「ここを直さないと記録として残す意味が薄い」というレベルの指摘だった。Claude Code がそのまま 3 件全部をメモに反映して、今日の作業を締めた。
筆者がやったことを並べると、
- measure-deploy.ps1 を叩いてビルド時間を計測した
pnpm generateが落ちたエラーログを読んで「ビルド本体は無事、検証スクリプトが落ちている」と切り分けた- 検証スクリプトの修正方針を Claude Code に指示した
- 長文ログを読ませる必要が出てきたので Opus 4.7 1M context に切り替えた
- ビルド数字に違和感を感じて Claude Code に過去ビルドとの比較分析を依頼した
- デプロイ済みサイトの表示崩れを目視で見つけた
- 「ドキュメント書いて Codex に投げて」とだけ言って退勤した
実装と分析と検証はほぼ Claude Code が回している。筆者がやったのは「方針を投げる係」と「画面の数字に違和感を持つ係」だけ。最後の Codex レビューも、自分で要点を読んで判断するのではなく、Claude Code に指摘を反映させるところまで任せた。
ビルド時間の改善余地を掴むつもりで叩いたスクリプトが、検証スクリプトのバグ・ハイドレーション崩れ・スローページの存在まで芋づる式に出してくれた。一日 1 スクリプトで 3 件の宿題が見えるなら、計測は儲かる。明日はスローページ特定から手をつける。