A single dynamic icon import blew up the dev server on a 16 GB Windows Subsystem for Linux 2 (WSL2) machine, forcing the vmmemWSL process to gobble all available memory and freeze the entire Linux window. The crash happened while running a Next.js 16 project that uses Turbopack, and it happened despite a .wslconfig file that caps the VM’s RAM usage.

なぜ小さなインポートがモンスター化するのか

16GBのメモリを搭載したWindows Subsystem for Linux 2 (WSL2) マシンにおいて、たった一つの動的なアイコンのインポートが開発サーバーをクラッシュさせ、vmmemWSLプロセスが利用可能なメモリをすべて食いつぶしてLinuxウィンドウ全体をフリーズさせました。このクラッシュは、Turbopackを使用するNext.js 16プロジェクトの実行中に発生しました。これは、.wslconfigファイルでVMのRAM使用量を制限していたにもかかわらず起こった出来事でした。

なぜ小さなインポートがモンスター化するのか

開発者は、動的なエントリポイントを使用して実行時にアイコンを解決しようとしました。そのインポートにより、約9,000個のモジュールを含むアイコンパッケージが読み込まれました。Next.js 16の開発サーバーを支えるRustベースのバンドラーであるTurbopackは、触れるすべてのパッケージに対して完全なモジュールマップを構築します。開発モードでは、このマップはメモリ上に保持され、ファイルが変更されるたびに更新されます。アイコンパッケージ全体をロードしたことで、Turbopackは大量のRAMを割り当てる必要に迫られ、.wslconfigファイルで設定された上限(ハードシーリング)にすぐに達してしまいました。制限に達すると、WSL VMは応答を停止しました。Ctrl + Cも効かず、唯一の解決策はWindowsホストの強制終了でした。

同じコードの本番ビルドは成功しました。これは、バンドラーがグラフを一度だけコンパイルし、アセットを出力して終了するためです。しかし、開発サーバーはホットリロードを可能にするために、グラフをメモリ上に保持し続けます。したがって、本番ビルドが成功したからといって、開発環境が同じインポートパターンに耐えられる保証はないのです。

より広範な影響

Windows内でLinuxベースのツールチェーンを使用している開発者は、リソース使用量を分離するためにWSL2に依存しています。単一のインポートがVMのメモリを使い果たすと、ホスト全体が動作の低下や応答不能に陥る可能性があり、同じマシン上の他のコンテナやアプリケーションにも影響を及ぼします。また、この事象は、一般的なNode.jsのメモリチューニングフラグとTurbopackのアーキテクチャとの間のミスマッチも浮き彫りにしました。TurbopackはV8ではなくRustで動作するため、Nodeの --max-old-space-size フラグを増やしても、そのRAM消費を抑えることはできません。

実際に何が起きたのか

  • 動的なエントリポイント: インポート文が、バンドラーに対してアイコンパッケージ全体を単一の遅延ロード(lazy-loaded)モジュールとして扱うよう要求しました。これに対しTurbopackは、モジュールマップを構築するためにすべてのファイルを即座に(eagerly)解析することで応答しました。
  • 開発サーバーのグラフ: 一回限りの本番コンパイルとは異なり、開発サーバーはファイル変更に対して即座にフィードバックを提供するために、完全な依存関係グラフをRAM内に保持します。
  • メモリ制限: .wslconfigファイルによって、VMはホストのRAMのわずかな一部に制限されていました。Turbopackの要求がその制限を超えたとき、VMはフリーズしました。

メモリ使用量を半分に削減する修正策

開発者は3つの実用的な変更を適用し、RAM使用量を3.6 GBから1.87 GBに削減して安定性を回復させました。

  1. 小さなアセットに対して動的なエントリポイントを使用しない – 必要なアイコンのみをインポートします(例: import { SearchIcon } from 'icon-pack/search')。数個だけであれば、インラインSVGの方がさらに軽量です。
  2. next.config.tsoptimizePackageImports を有効にする – このオプションは、Turbopackに対してインポートをパッケージのルートではなく、具体的なファイルパスに解決するよう指示し、パッケージツリー全体のロードを防ぎます。
  3. Nodeのヒープフラグに頼らない – Turbopackのメモリ使用量はRustのランタイムによって制御されているため、--max-old-space-size はこの問題に対して効果がありません。

.wslconfigの制限はそのままにしておくことが推奨されます。ハードキャップ(厳格な制限)を設けておくことで、WindowsホストがクラッシュするのではなくVMが一時停止するだけで済む可能性があり、すべてが停止する前に介入する猶予が生まれます。

自身のワークフローで注意すべき点

  • メモリダッシュボード: WSL内の htop やWindowsのタスクマネージャーなどのツールを使用して、vmmemWSLが急増するタイミングを確認できます。使用量が設定した制限に近づいた場合にアラートを設定してください。
  • パッケージサイズの監査: ライブラリを追加する前に、それがいくつのモジュールを含んでいるかを確認してください。大規模なアイコンパック、ユーティリティコレクション、またはコンポーネントライブラリは、開発時のグラフを密かに肥大化させる可能性があります。
  • 選択的なインポート: 特にバンドラーがすべてをメモリに保持する開発環境では、ワイルドカードインポートや動的インポートよりも、名前付きインポートや直接的なファイルパスを優先してください。
  • 本番環境と開発環境の差異(パリティ): 本番ビルドの成功は、それとは別の検証ステップとして扱ってください。ホットリロード中にのみ発生する問題を捉えるために、メモリモニターを使用しながら開発サーバーを実行してください。

まとめ

たった一つの動的にインポートされたアイコンパッケージが、ホストのリソースを意図的に制限している場合でも、WSL2ベースのNext.js開発サーバーを機能不全に陥らせるほどのRAMを消費することがあります。広範な動的インポートを避け、パッケージレベルのインポート最適化を有効にし、メモリ使用量を監視することで、開発者はWindows内のLinux環境のレスポンスを維持し、強制終了を回避することができます。