Next.jsのビルドをVPSの外へ置くとき、外側ではどのくらいメモリを使うのか。このサイト(prodnote)の静的エクスポートを、8GBの開発機で1回測りました。

前の記事では、2GBのVPSで別アプリをビルドし、スワップが140MiB増えました。今回はアプリも機械も違います。同じアプリのビルドを外へ移した比較試験ではありません。

また、prodnoteは配信もCloudflare Pagesに任せています。サーバーアプリを外でビルドしてVPSへ配る手順を検証した記事ではありません。

先に、結果だけ

機械              開発機。WSL2 Ubuntu / メモリ 7,942 MiB / 4コア
対象              prodnote(Next.js 15.5 の静的エクスポート・記事12本)
コマンド           .next を消してから npm run build

終了コード          0
所要時間           29.22秒
ピークRSS          529,748 KB(= 517 MiB)
CPU使用率          191%
available         7,474 → 6,903 MiB   ビルド中の最小値。571MiB 減った
スワップ            0 → 0 MiB          増えていない
Major page faults  24
サンプル            1秒間隔で30点

ビルド後のサイズ     .next 94MB / out 5.7MB / node_modules 598MB

スワップは動きませんでした。前の記事でいちばん引っかかっていた数字が、こちらでは 0 のままです。

何を、どう測ったか

記事12 と同じ scripts/measure-build.sh に包みました。中でビルドを実行しながら、1秒ごとに free の値を書き出すだけのスクリプトです。方法を揃えたのは、数字の出どころを同じにしておきたかったからです。

サンプルは30点しかありません。ビルドが29秒で終わるので、1秒間隔ではこれ以上取れません。記事12 は205点でした。ここは記録の細かさが違います。

.next を先に消したのは、前の記事の --no-cache と同じ理由です。残っているものに当たると、測っているのがビルドなのかキャッシュの読み出しなのか分からなくなります。

ただし node_modules は消していません。598MB がそのまま置いてある状態からの計測です。記事12 のほうは npm ci から走っているので、ここは条件が違います。

この2つは比べられません

条件が3つ違います。

prodnote(今回)記事12(サーバーの中)
機械開発機 8GB / 4コアVPS 2GB / 3コア
アプリ静的エクスポートフロントエンドのビルド(6コンテナが動いている箱の中で)
工程node_modules は既にある--no-cache で npm ci から

29.22秒と3分26秒を並べて「外のほうが速い」と読むことはできません。別の機械で、別のものを、別の工程で測った2つの数字です。

比べられるのは、測り方の限界のほうでした。

記事12 で取れなかった数字が、今回は取れた

前の記事には、失敗の記録が1つ入っています。ピークRSSを取ろうとして、7,168 KB という値が出てきた件です。

記事12   ピークRSS 7,168 KB    docker CLI 自身の値
今回     ピークRSS 529,748 KB  = 517 MiB

/usr/bin/time -v は、包んだプロセスとその子プロセスまでを見ます。ところが docker build の実体は Docker のビルドエンジンが別に走らせるので、time から見える範囲の外に出てしまいます。返ってきていたのは、命令を出した側のコマンドが使ったメモリでした。

今回は npm run build を直接包みました。ビルドはその子プロセスとして走るので、time が届きます。517MiB という数字は、こうして初めて手に入りました。

同じことを測ろうとして、片方は届いて片方は届きませんでした。道具の届く範囲は、包み方で変わります。

2つの数字を足して、必要容量は決められない

前の記事のavailable減少と、今回のピークRSSは別の指標です。

記事12 の実測   VPSのビルド中に available が 578MiB 減り、470MiB まで落ちた
今回の実測      prodnote のビルドは、ピークで 517MiB を載せた

この記事では以前、この2つを単純に足してVPSの空きと比べていました。必要容量の判断には使えないので、その計算は取り下げます(2026年9月8日訂正)。元の実測値は変えていません。

機械・アプリ・工程に加え、測っている範囲が違います。片方はシステム全体の available の減り方で、もう片方は time が観測したプロセスのピークRSSです。

この2つが別物であることは、今回の記録が自分で示しています。available は571MiB 減りましたが、ピークRSSは517MiB でした。54MiB ずれています。 available の減り方はシステム全体の増減で、プロセス1つが載せた量そのものを表す数字ではありません(available が見積もりであることは前の記事に書きました)。54MiB の内訳までは、この記録では切り分けていません。

同じ箱で動かした場合は、ページキャッシュや処理が重なる時点によって結果が変わります。この記録から「同居できない」「2GBでは足りない」とは判断できません。

同居した場合の測定はしていません。稼働中のサイトへ負荷をかけて、この問いを確かめることもしません。

そもそも、このサイトには本番サーバーが無い

ここまで「サーバーの外でビルドする」と書いてきましたが、prodnote の場合、外に出したのはビルドだけではありません。

記事を書いて GitHub に push すると、Cloudflare Pages がビルドと配信をやります。私が持っているサーバーは、この工程のどこにも出てきません。ビルドが1バイトも乗らないのではなく、乗る先が無いのです。

公式のドキュメントには、無料プランの制限がこう書かれています(2026年9月2日に確認)。

Free: Builds 1 build at a time / Builds per month 500 Builds will timeout after 20 minutes.

(Cloudflare Docs — Pages Limits)

同時に1つ、月に500回、20分でタイムアウトします。記事を書いて push する頻度なら、月500回は当たりません。

⚠️ ビルド機のメモリとCPUがいくつなのかは、公式ドキュメントの中で見つけられませんでした。 書かれていないと言い切るつもりはありません。私が探した範囲で見つからなかった、というだけです。

向こうの機械の数字も見ました。前の記事を公開したときのデプロイ(2026年8月30日)の内訳です。

工程時間
initialize3秒
clone repo2秒
build59秒
deploy9秒
合計1分13秒

ビルドは59秒でした。 20分のタイムアウトに対して、5%ほどです。

⚠️ 手元の29秒と並べて「向こうが遅い」とは読まないでください。 Cloudflare は毎回まっさらな clone から始めるので、この59秒の中では依存パッケージの取得も走っているはずです。⚠️ ログの内訳までは見ていません。手元は node_modules が既にある状態で測りました。工程が違います。

また、59秒は記事12本のときの数字です。記事数やビルド環境が変わった後も同じ時間になるとは限りません。

記事01 でやめた判断の、材料が1つ増えた

このサイトを作ったとき、最初は稼働中のVPSに同居させるつもりでした。やめた理由を1本目の記事にこう書いています。

2GB のうち既存の Postgres + Django + Next.js + nginx で大半を使っている

当時これは、動かしている感触からの判断でした。メモリを測ったわけでも、ビルドを走らせて比べたわけでもありません。

今回の517MiB と、記事12 の578MiB が、あとから材料として出てきた形になります。⚠️ 前者はピークRSS、後者は available の減り方で、種類の違う数字です。ただ、これで当時の判断が正しかったと証明されたわけではありません。同居させて測っていない以上、証明にはなりようがない。やめたので、やめなかった場合が観測できない。 そういう種類の判断でした。

この記録で得られたのは、別々の条件で測った数字です。同居を避けた判断の正しさや、必要なVPSプランを証明するものではありません。

ビルドの置き場所は、プランより先に決まる

記事12 の最後に、こう書きました。

決めるときに先に見るのは、プランの数字よりも「どこでビルドするか」だと思います。

今回測ってみて、その順番は変えなくていいと思っています。理由は速さではなく、測れる場所が変わるからです。

サーバーの中でビルドすると、稼働中のアプリと同じメモリを使います。記事12では、その状態でスワップが140MiB増えました。別環境で成果物やDockerイメージを作ってVPSへ配れば、そのビルド処理を外せます。ただし、VPSに残るアプリ・DB・更新処理などの容量は引き続き考える必要があります。

prodnoteはさらに、ビルド結果を静的ファイルとしてCloudflare Pagesで配る形です。こちらではビルド回数や時間の制限に加えて、受信リクエスト依存のサーバー処理を静的ファイルだけでは行えない制約があります。ブラウザーから別のAPIを呼んで内容を変える構成は可能ですが、そのAPIの配置と運用は別に必要です。

外部ビルドと静的配信の区別は記事02の後半にもまとめました。構成の説明はNext.js 15 Self-HostingとStatic Exportsを確認しています(2026年9月8日)。今回の1回の計測だけで、別アプリの置き場所や必要容量までは決まりません。

私の環境にあって、あなたの環境に無いかもしれないもの

同じ数字にならない条件を並べておきます。

  • 開発機のメモリが 7,942MiB あり、ビルド前の available は 7,474MiB でした。スワップに逃がす場面が来ていません。手元のPCが2GBや4GBなら、この記録は参考になりません
  • node_modules が598MB、既に展開済みの状態から始めています。npm ci からの計測には依存関係の取得・展開も入りますが、今回はその工程の時間やピークメモリを測っていません
  • 記事が12本です。記事数が増えれば書き出すページも増えます
  • 画像は別のコマンドで先に作ってあり、next.config.ts でも最適化を切っています(images: { unoptimized: true })。ビルドの中で画像を変換する構成なら、そのぶんが乗ります
  • 型チェックと ESLint は切っていません。next build の中で走るので、29秒にはその時間も入っています
  • 記事本文は react-markdown でビルド時に HTML にしています。Markdown の処理系が違えば、ここの重さも違います
  • CSS は Tailwind CSS と PostCSS で、ビルドの中で生成しています。CSSの作り方が違えば、この時間も違います
  • データベースを使っていません。ビルド中に外部へ問い合わせる処理がゼロです
  • Web フォントを読み込んでいません。フォントのサブセット化を挟む構成なら、その分が乗ります
  • Node のヒープ上限を掛けていません。NODE_OPTIONS の指定はこのリポジトリのどこにもありません
  • WSL2 の上の Ubuntu です。Windows のファイルシステムをまたぐ構成だと、ディスクI/Oの条件が変わります

測っていないこと、書けないこと

  • 同居させた場合の実測。 異なる環境・指標の数字を足しても、同居時の必要容量は求められません
  • Cloudflare Pages のビルド機のスペック。 公式ドキュメントで見つけられませんでした。⚠️ 「無い」ではなく「見つけられなかった」です。 ビルド時間のほうは1回ぶんを見ました(59秒)が、何回か回して振れ幅を見たわけではありません
  • 他社のホスティング。 使っていないので数字がありません。仕様の比較もしていません
  • ⚠️ 開発機の Node のバージョンは、計測中に記録し忘れました。 測り終えたあとに確認して v20.20.2(npm 10.8.2)でした。環境は計測の前後で変えていないので同じはずですが、計測の記録として取ったものではありません。バージョン差が出る計測なら、押さえておくべき項目でした
  • 複数回の計測。 1回きりです。29秒の処理なので繰り返せるはずで、やっていないのはこちらの手落ちです
  • npm run build の内訳。 30点のサンプルはありますが、どの工程でピークが来たかまでは切り分けていません

まとめ

  • サーバーの外(開発機)で prodnote をコールドビルドし、29.22秒・終了コード 0・ピークRSS 517MiB。スワップは 0 のまま動かなかった
  • 記事12 で取り逃がしたピークRSSが、今回は取れた。docker build を time で包むと届かず、npm run build を直接包むと届く
  • ⚠️ 記事12 の数字とは、機械もアプリも工程も違う。速い遅いの比較には使えない
  • 別環境のavailable減少とピークRSSを足して、必要容量や同居の可否は判断できない
  • このサイトには本番サーバーが無い。向こうのビルドは59秒で、20分の制限に対して5%ほど。⚠️ ビルド機のスペックは公式で見つけられなかった
  • 外部ビルドと静的配信は別の選択肢。静的な画面からAPIを呼ぶ構成では、そのAPI側の処理も別途必要