OpenAINext.jsFeb 19, 2019, 2:00 PM

Next.js 8 Webpack Memory Improvements

A condensed section focused on the key takeaways first.

Original Post

Quick Digest

Summary

A condensed section focused on the key takeaways first.

openaienmodel: gpt-5-mini-2025-08-07

Next.js 8 Webpack Memory Improvements

Key Points

  • concurrency-limited asset writes
  • enable output.futureEmitAssets
  • allocations reduced to ~184KB chunks

Summary

Next.js 8 includes upstream webpack optimizations that drastically reduce build-time heap usage when compiling many pages. The team identified large, simultaneous allocations caused by calling asset.source() concurrently for every file and by CachedSource holding generated output in memory. They fixed it by limiting concurrent asset writes and opting into a new webpack emit behavior that avoids unnecessary in-memory caching.

Key Points

  • Problem: webpack was generating all asset sources concurrently (via asyncLib.forEach), causing very large one-time allocations (observed ~548 MB) that scaled with number of pages.
  • Fix 1: Limit concurrency when iterating compilation.assets (e.g., asyncLib.forEachLimit with a practical limit like 15) to spread allocations over time (reduced chunks to ~34 MB).
  • Fix 2: Use webpack's output.futureEmitAssets behavior to avoid keeping CachedSource results in memory after emit (further reduced allocation chunks to ~184 KB).
  • Result: Significant drop in peak and incremental allocations; Next.js 8 ships these changes by default, but webpack users can opt in to the same improvements.
  • Practical advice for engineers:
    • Upgrade to Next.js 8 (or a webpack release that includes output.futureEmitAssets) to get the fixes out of the box.
    • If running custom build pipelines, limit concurrent asset writes when iterating compilation.assets (start with ~15) and enable output.futureEmitAssets when available.
    • Profile builds with Chrome DevTools/Node inspector to verify allocation patterns and tune concurrency.

Full Translation

Translations

A translation section that keeps the flow of the original article.

openaijamodel: gpt-5-mini-2025-08-07

Next.js 8 の webpack メモリ改善

Next.js 8 の webpack メモリ改善

投稿日: 2019-02-19

投稿者: Connor Davis (@connordav_is), Tim Neutkens (@timneutkens)

最近、Next.js 8 がリリースされました。このリリースにはビルド時のメモリ使用量を大幅に削減する改善が含まれており、本記事ではコミュニティのために我々がどのように webpack を最適化したかを解説します。

Next.js はゼロコンフィグを目指し、webpackBabel のようなツールの上に構築されています。目的は重要なこと、つまりアプリケーションのコードに集中できるようにすることです。

モダンなウェブアプリケーションは、ホームページ、ブログ、ダッシュボード、商品一覧など、1ページまたは複数ページで構成されます。Next.js ではこれらのページがプロジェクトルートの特殊な pages ディレクトリ内のファイルになります。例えば pages/about.js は URL /about にマップされます。

フレームワーク設計上の重要な制約の1つは、単一ページから数千ページまでどちらにも適切に動作する必要があることです。

問題の発見

Serverless Next.js を実装する過程で、何百ページもあるプロジェクトで next build を実行するとメモリ使用量が高くなり、Node.js のヒープ制限(約 1.4 GB)を超えることがあることに気づきました。

ビルドプロセスのメモリ使用量を Chrome Developer Tools でプロファイリングしたところ、webpack が一度に 548 MB のメモリを確保するポイントを発見しました。確保されるメモリ量はページ数と相関しており、ページが多いほどメモリ使用量が増えていました。

プロファイルのスタックトレースを辿ることで、メモリ割り当てのスパイクを引き起こしている関数を特定しました。割り当ては source.source() メソッドが呼ばれることに由来しており、このメソッドは生成されたファイルをメモリに格納します。

さらに上の呼び出し元を見ると、compilation.assetsasyncLib.forEach で反復しており、提供された関数が compilation.assets 配列内のすべてのファイルに対して同時に呼ばれていることが分かりました。つまり、例えば 100 ページある場合、ディスクに書き出すために 100 個のファイルを同時に生成・書き出そうとしてしまっていました。

同時書き込み数の制限

この問題の解決策は、セマフォを使って同時書き込みの数を制限することです。通常は async-sema を使いますが、このケースでは webpack が既に利用している neo-asyncasyncLib.forEach に適切なメソッドがありました。

従来の同時実行(すべてのアセットで関数を同時に実行):

asyncLib.forEach(compilation.assets, (source, file, callback) => {
  // etc
});

同時実行数を制限したコード(最大 15 並列):

asyncLib.forEachLimit(compilation.assets, 15, (source, file, callback) => {
  // etc
});

この同時実行制限を導入して再プロファイリングしたところ、メモリ割り当てが一度に 34 MB 程度ずつ小分けにされるようになりました。

キャッシュされたアセットとガベージコレクション

ただし、この変更だけでは実運用でビルドがまだメモリ不足になることがあり、さらなる調査を続けました。メモリプロファイルを詳しく見ると、source.source() を呼んだ後にメモリが解放(ガベージコレクション)されていないことが分かりました。

webpack のアセットは通常 Source クラスのインスタンスで、これらは source() メソッドを実装してファイルソースを生成します。プロファイルでは多くのアセットが CachedSource のインスタンスであることが示されていました。CachedSourcesource() が呼ばれると結果をメモリにキャッシュし、アセットが dispose されるまで保持します。

Next.js が使っている webpack プラグインを調べたところ、webpack がファイルを書き出した後に source() を呼ぶプラグインは存在せず、書き出した値をキャッシュすることに利点がないことが分かりました。

output.futureEmitAssets の導入

Tobias Koppers と協力した結果、output.futureEmitAssets という新しいオプションが実装され、 새로운アセット書き出しの挙動を opt-in できるようになりました。この新しい挙動により、時間経過で割り当てられるチャンクサイズは 182 KB 程度まで減少しました。

最終的な最適化後のプロファイラでは、時間経過で 184 KB 程度のチャンクが割り当てられているのが確認できます。

結果と影響

Next.js 8 にはこれらの最適化がすでに組み込まれています。Next.js を使用する際に何かを変更する必要はありません。この最適化は webpack 側で導入されたため、Next.js ユーザーだけでなくすべての webpack ユーザーが恩恵を受けます。

今後も Next.js と webpack のメモリ使用量とパフォーマンスの改善を継続して行っていきます。