Vercelのビルドがエラーメッセージ1行も出さずに落ちた話

型チェックもコンパイルも通ったあと、静的生成の開始直後に無言で終了していました。原因はコードではありませんでした。

出た数値

ビルドが止まった位置
Generating static pages (0/510) の直後
エラー出力
0行
全510ページのうち画像生成ページ
212ページ
手元(16GB・4コア)での結果
成功
ワーカーを1本に絞った場合のビルド時間
131秒

出ていたログ

デプロイが失敗し、ログの末尾はこうなっていました。

「Compiled successfully in 12.8s」「Linting and checking validity of types ...」「Collecting page data ...」「Generating static pages (0/510) ...」、そして「Command "npm run build" exited with 1」。

**エラーメッセージが1行もありません。** コンパイルも型チェックも通っていて、静的生成が始まった直後に終わっています。

ここで1つ手がかりがあります。ページ側で例外が出た場合、Next.jsは「Error occurred prerendering page ...」という行を必ず出します。それが無いということは、**JavaScriptの例外ではなく、プロセスそのものが外から強制終了された**可能性が高い、という読みになります。

まず手元で同じ条件を作った

「手元では動くのに」で止まらないよう、ビルド環境を再現しました。やったのは4つです。

1つ目、作業ツリーの汚れを排除するため、リポジトリを真っさらに展開し直しました。手元のフォルダには実験の残骸が入っていることがあるので、これを疑わなくて済む状態にします。2つ目、デプロイ時に除外されるファイル群を実際に削除しました。除外設定があるということは、本番には無いファイルがあるということです。3つ目、依存関係をロックファイルからクリーンに入れ直しました。4つ目、ビルドキャッシュを消して素の状態で実行しました。

**結果は成功でした。** 全510ページが生成されます。

さらに、ログに出ていたバージョン(Next.js、画像処理ライブラリ、ビルドツール)を手元のロックファイルと突き合わせたところ、**すべて一致していました。** 依存関係の差でもありません。残る違いは、実行環境そのものだけになりました。

紛らわしかったが無関係だったもの

ログには警告も出ていました。インストール時に実行されるスクリプトが2つ実行されていない、という内容です。画像処理ライブラリの名前が入っていたので、最初はここを疑いました。

調べたところ無関係でした。このライブラリは環境ごとにビルド済みのものが別途入る仕組みになっていて、**インストールスクリプトが走らなくても動作します。**

ログに出ている警告と、実際の失敗原因は別のことが多いです。ここで時間を使わないよう、警告を見つけたらまず「これが原因なら、どういう症状になるはずか」を考えてから調べたほうが早いです。今回の症状(無言で終了)は、スクリプト未実行では説明がつきませんでした。

原因は画像生成の並列度だった

全510ページの内訳を見て気づきました。**212ページがOGP画像の生成ページ**です。ページごとにSNS用の画像を1枚ずつ作っていて、1枚あたり描画用のライブラリとフォントを読み込みます。

静的生成は複数のワーカーで並列に走り、その数は既定でCPUのコア数です。**コア数が多い環境ほど、同時に立ち上がるワーカーが増え、合計の使用量が増えます。** 手元の16GB環境では収まっていたものが、ビルド環境では収まらなかった、という話でした。

無言で終了していたのは、メモリを使い切ったプロセスが外部から強制終了されたためです。例外ではないので、当然エラー出力もありません。

直し方と、代償の測り方

対処は、ビルド環境でのワーカー数を1本に固定することです。設定1行で変えられます。

ただし直列化すればビルドは遅くなるので、**採用する前に測りました。** ワーカー1本でも510ページのビルドは131秒で終わります。制限時間には遠く及ばないので、代償はありませんでした。この確認をせずに設定を変えると、次はタイムアウトで落ちるだけです。

もう1つ記録しておくべきことがあります。**この負荷は今後も増え続けます。** コンテンツを1つ足すたびに画像生成ページも1つ増えるためです。ワーカー1本でいずれ足りなくなったら、そのときは並列度をいじるより、ページごとの画像生成をやめて共通の1枚に寄せるほうが根本的に効きます。設定ファイルのコメントにその旨を残しました。

教訓

**エラーメッセージが無いこと自体が手がかりです。** 例外なら必ず何か出ます。何も出ずに終了コードだけが返るなら、プロセスが外から止められたと考えて、原因をコードの外に探すほうが早いです。

もう1つ。「手元では動く」で止まらず、**同じ条件を手元に作る**のが結局いちばん速い切り分けでした。真っさらに展開して、除外ファイルを消して、依存を入れ直して、キャッシュ無しで実行する。ここまでやって成功したので、コードの可能性を消せました。あとは環境の差を探すだけになります。

関連するページ

広告枠