はじめに
😱「昨日まで普通に動いてたのに、急に next dev が重い…」
🫠「画面遷移のたびにメモリがどんどん増えて、最終的に固まる…」
😰「アクティビティモニタを見たら next-server が 10GB 超えてる…!?」
ある日突然、開発サーバーがメモリを食い潰して開発にならなくなりました💦
「サービスの規模が大きくなったからかな…」と最初は思ったのですが、調べていくと真犯人はソースコードでもアプリの規模でもなく、"プロジェクトの外" に紛れ込んだ 1 つのゴミファイルでした🕵️
今回は、next dev(Next.js 16 + Turbopack)のメモリ暴走をデバッグして真因にたどり着くまでの記録です🔍
同じように「dev サーバーが急に重くなった」方の助けになれば幸いです🙏
結論:親ディレクトリに紛れた「空の package-lock.json」がワークスペースルートを誤検出させていた
先に結論です。原因はこれでした👇
プロジェクトより上位のディレクトリに
package-lock.jsonが紛れ込んでいると、Next.js がそこを「ワークスペースのルート」だと誤検出し、Turbopack が無関係なディレクトリまで丸ごと監視・走査してメモリが暴走する。
私の環境では、プロジェクトの 2 階層上に、中身が空っぽの package-lock.json(過去の誤操作の残骸)が転がっていました。
_prj/
├── package-lock.json ← 🗑️ 中身が空のゴミ(過去の誤 npm install の残骸)
├── project/
│ └── main-app/ ← 本当のプロジェクト
│ └── package-lock.json ← ✅ 本物
├── other-project-a/ ┐
├── other-project-b/ ├─ 無関係な兄弟プロジェクト(合計15GB超)
└── other-project-c/ ┘
このゴミファイルを削除しただけで、next-server のメモリは 10GB → 約2.5GB に激減しました⚡
コードは 1 行も変えていません。
以下、なぜこれが起きるのかと、たどり着くまでの回り道を書いていきます。
症状:遷移のたびにメモリが増え続け、最後は固まる
まず症状はこんな感じでした。
-
next dev起動直後は普通 - 画面を何回か遷移する(=ルートを再コンパイルする)たびに、じわじわメモリが増える📈
- 最終的に 10GB を超え、リンクをクリックしてから遷移完了まで数十秒〜数分かかる
- ひどいときは Mac ごとフリーズ🧊
「規模が大きくなったから重いのかな?」と思いがちですが、ページ数28・コンポーネント176 程度の中規模アプリで 10GB は明らかに異常です。
犯人探しの回り道①:「react-icons が重いんでしょ?」→ Turbopack では効かなかった
最初に疑ったのは、アイコンライブラリの react-icons でした。
「大量のアイコンを束ねたバレルファイルを import しているから、コンパイルが重いに違いない。optimizePackageImports に追加すれば直る」——そう考えたわけです。
// next.config.ts(当初やろうとした対策)
const nextConfig = {
experimental: {
optimizePackageImports: ['react-icons/fa6'],
},
}
ところが、Next.js の公式ドキュメントを読んで前提が崩れました😇
Turbopack automatically analyzes imports and optimizes them. It does not require this configuration.
(Turbopack は import を自動で解析・最適化するので、この設定は不要です)
そう、Next.js 16 では next dev のデフォルトバンドラが Turbopack で、Turbopack は import を自動最適化するため、optimizePackageImports は no-op(何もしない)なんです。しかも react-icons/* はもともとデフォルトの最適化対象リストに入っています。
つまり「react-icons バレル説」はそもそも的外れでした🙅
「昔の Next.js(webpack 時代)の知識」で対策すると、Turbopack では空振りすることがあります。バンドラが変わると前提も変わるので、公式ドキュメントで裏を取るのが大事でした📖
犯人探しの回り道②:AI に任せたら、逆に悪化して90GBまで暴走した話
実はこのデバッグ、Claudeにも手伝ってもらっていました🤖
次に試したのは、dev サーバーのメモリを下げる正攻法とされる preloadEntriesOnStart: false(起動時プリロードの無効化)など。多少は効きますが、10GB → 2.5GB のような劇的な改善にはなりません。
さらにここで事件が起きます。Claude が検証の過程で turbopack.root を誤った値に設定したまま、dev サーバーを複数同時に起動してしまい、なんとメモリ表示が一時的に 90GB 近くまで跳ね上がって Mac がフリーズしました🧊💥(この「90GB」のカラクリは後述します)
AI に任せきりにすると、こういう"検証の暴走"も起こり得るんだな…と冷や汗をかきつつ🥶、「これは設定を小手先でいじる話じゃない。もっと根本のどこかがおかしい」と気づきます。
念のため補足すると、Claude を悪者にしたいわけではありません😌 このあとの真因特定も、実は Claude と二人三脚で辿り着いたものです。ハマるときは人間も AI もハマりますが、それでも Claude は信頼できる最強の仲間🤝 大事なのは「AI に丸投げ」ではなく「一緒に手を動かして検証する」ことだと、改めて感じました。
決定的な手がかり:起動ログの警告を見逃していた
ここで、next dev の起動ログをちゃんと読み直したら、ずっと出ていた警告に気づきました👀
⚠ Next.js inferred your workspace root, but it may not be correct.
We detected multiple lockfiles and selected the directory of
/Users/xxx/_prj/package-lock.json as the root directory.
「複数の lockfile を検出したので、_prj/package-lock.json のあるディレクトリをルートに選びました」
……つまり Next.js は、私のプロジェクト(main-app)ではなく、2 階層上の _prj/ をワークスペースのルートだと思い込んでいたのです😲
これが真犯人でした。
真犯人の仕組み:ワークスペースルートの誤検出
Next.js はどうやってルートを決めるのか
Next.js(Turbopack)は、「最も上位にある lockfile」をワークスペースのルートとして扱います。モノレポ対応のための挙動です。
私の環境ではこうなっていました👇
🔍 Next が上に向かって lockfile を探す
↓
main-app/package-lock.json ← 見つけた(でもまだ上を探す)
↓
_prj/project/ ← lockfile なし
↓
_prj/package-lock.json ← 🗑️ 見つけた!(これが最上位)
↓
📦 「ルートは _prj/ だ!」と誤検出
誤検出すると何が起きるか
ルートが _prj/ になると、Turbopack はそのルート配下を監視・走査の対象にします。ところが _prj/ の下には、メインアプリとは無関係な別プロジェクトが15個・合計15GB超が並んでいました。
😇 本来監視すべき範囲 😱 実際に監視していた範囲
┌─────────────┐ ┌───────────────────────────┐
│ main-app/ │ │ _prj/ 全体(15GB超) │
│ (数百MB) │ → │ ├ main-app/ │
└─────────────┘ │ ├ other-project-a/ │
│ ├ other-project-b/ │
│ ├ … × 15個 │
│ └ 全部の node_modules │
└───────────────────────────┘
15GB のファイルツリーを監視し、依存グラフをメモリに抱え込もうとすれば、そりゃメモリも暴走します💥
遷移のたびに増えていくのも、Turbopack が遅延コンパイル(開いたページだけ処理)で、触るほどに巨大ツリーの解決が積み上がるからでした。
「なぜ今日、急に」問題:スケール × 2サーバー併走で閾値を超えた
ここで 1 つ疑問が残ります。
🤔「この lockfile、数か月前からあったのに、なんで今日まで平気だったの?」
調べると、構成自体(Next のバージョンもバンドラも)はずっと変わっていませんでした。罠は最初から仕込まれていたわけです。じゃあ何が変わったのか。
答えは "量" の蓄積が閾値を超えたこと。効いた要因は 2 つあり、主因はメインアプリ自身の規模でした。
- 【主因】メインアプリの規模が大きくなった:ページ・コンポーネントが増え、dev で扱うモジュールが増えた。ルート誤検出という土台の上でこれが膨らみ、メインアプリ単体で 約10GB に到達した
- 【副因】2 つ目の dev サーバーの併走:同じ親ディレクトリ配下に**管理画面用の別 Next アプリ(admin)**を作り始め、2 つの dev サーバーを同時起動するようになった
面白いのは、同じルート誤検出でもアプリの規模で結果が全然違ったこと。実測では メインアプリ ≒ 10GB / 管理画面アプリ ≒ 2GB でした。同じ 15GB ツリーを誤検出していても、小さいアプリはそこまで膨らみません(Turbopack は遅延コンパイルで、実際に食う量はアプリの規模・使い込み方に比例するため)。
つまり「開発が止まる」レベルの 10GB は主にメインアプリの規模 × ルート誤検出で生まれていて、2 サーバー併走(10GB + 2GB ≒ 12GB)は物理 18GB を超えてフリーズさせる最後のひと押し、という役割分担でした。
「規模が大きくなったから重いのでは?」という最初の直感は、実は半分正解でした。規模は主因。ただし規模そのものが 10GB を生んだのではなく、ルート誤検出が規模を何倍にも増幅していたのが実態です。
余談:18GB の Mac で「90GB」と表示されたのはなぜ?
検証中に「メモリ 90GB」と警告が出た件、気になりますよね🤔
でも冷静に考えると、私の Mac のメモリは 18GB。90GB なんて物理的に載るはずがありません。
実は OS が言う「メモリ使用量」には、"本当に RAM を使っている分" 以外もいろいろ混ざっているんです。机にたとえると、こんな感じ👇
- 🗄️ 「使うかも」と場所だけ予約した領域:実際には何も置いていないのに「確保済み」とカウントされる(=空の予約席)
- 📄 中身はディスクにあるファイルを"参照"しているだけの領域:15GB のファイルを大量に参照すると、その分も「メモリ扱い」で大きく計上される。でも実体はディスク上にある
- 📦 机からあふれて別の場所に逃がした分:RAM が足りなくなると、macOS は中身を圧縮したり SSD に退避したりする。これも合計に乗る
つまり「90GB を RAM に詰め込んだ」わけではありません。実態はこうでした👇
18GB の机(RAM)を使い切り、あふれた書類を SSD に出し入れし続ける。その"出し入れ"に忙殺されて、パソコン全体が固まった。
この「出し入れの渋滞」を スラッシングと呼びます。フリーズの正体はこれでした🧊
ちなみに、ps コマンドで測れる "本当に RAM に載っている量" は数十〜数百MB しかありませんでした。「実際の RAM 使用は小さいのに、警告の数字だけ巨大」という一見ちぐはぐな状況も、これで説明がつきます😌
危うくMacが吹き飛ぶんじゃないかと恐怖を覚えました…心臓に悪い…☠️
対処法
✅ 1. 余計な lockfile を削除する(本命)
プロジェクトより上位にある不要な lockfile を探して消します。
# プロジェクトより上位に lockfile が無いか探す
find ../.. -maxdepth 2 \( -name 'package-lock.json' -o -name 'pnpm-lock.yaml' -o -name 'yarn.lock' \)
出てきたファイルの中身が空っぽ("packages": {} だけ 等)で、package.json も伴っていなければ、過去の誤 npm install の残骸なので削除して OK です🗑️
rm ../../package-lock.json
これで Next の自動検出が本来のプロジェクトに戻ります。
✅ 2. ビルドキャッシュ .next を消してから再起動する
設定やルートが変わったあとは、誤ったルートがビルドキャッシュに焼き付いていることがあります。一度消してから起動しましょう。
rm -rf .next && npm run dev
再起動後、起動ログにあの警告が出ないこと・メモリが正常域(このアプリ規模で数百MB〜数GB)に収まることを確認します👌
🔧 (代替)turbopack.root で明示的に固定する
lockfile を消せない事情がある場合は、Next の警告文自身が勧めている通り、turbopack.root でルートを明示する手もあります。
// next.config.ts
const nextConfig = {
turbopack: {
root: process.cwd(), // プロジェクトルートに固定
},
}
※ 設定変更後は同じく .next を消してから起動してください。
🩺 診断に使えるコマンド
# dev 稼働中の next-server の物理メモリ(RSS)を見る(KB表示)
# grep パターンを [n]ext-server にして grep 自身にマッチさせない
ps -o rss=,command= -ax | grep -i '[n]ext-server'
再発防止と教訓
同じ轍を踏まないためのチェックリストです👇
✅ dev サーバーが急に重くなったら…
1. 起動ログの「inferred your workspace root / multiple lockfiles」警告を必ず読む
→ 出ていたら、ワークスペースルート誤検出をまず疑う
2. プロジェクトより上位に余計な lockfile が無いか find で確認
→ 空のゴミ lockfile は削除
3. 複数プロジェクトを同じ親配下に並べるなら、
→ 共通の親に lockfile を作らない(両アプリが親を誤検出する)
4. 設定を変えたら .next を消してから再起動
5. Turbopack では optimizePackageImports は基本 no-op
→ 「昔の webpack 時代の対策」を鵜呑みにしない
まとめ
🎯 重要なポイント
-
next devが急に重くなったら、まず起動ログの警告を読む:inferred your workspace rootはワークスペースルート誤検出のサイン⚠️ - 上位ディレクトリの余計な lockfile が真犯人になり得る:Next は最上位の lockfile をルートに選ぶため、無関係な巨大ツリーまで監視してしまう🗑️
- 主因はアプリの規模、それをルート誤検出が増幅した:規模の成長を誤検出が何倍にも膨らませ 10GB に。2 サーバー併走はフリーズへの"最後のひと押し"✖️
- 「90GB」は物理 RAM ではなく仮想/mmap/スワップの会計値:実害はスラッシングによるフリーズ🧊
-
Turbopack では
optimizePackageImportsは no-op:バンドラが変われば前提も変わる。公式ドキュメントで裏を取る📖
🚀 個人的な学び
- 「重い=アプリの規模のせい」と決めつけると回り道する。まず起動ログとメモリの実測から入るべきでした👀
- 環境の"外側"(プロジェクトの外のファイル)が原因になることがある。コードだけ見ていても見つからない🌍
- 最終的な修正は「ゴミファイルを 1 つ消す」だけ。いちばんシンプルで正しい着地でした🍵
シンプルなメソッドや設定ほど、"どこを見て動いているか" を正しく理解しておくことがトラブル解決の近道ですね💪!
最後に
next dev のメモリ問題、原因が「プロジェクトの外」にあるとは思いませんでした🍵
皆さんも「昨日まで動いてたのに急に重くなった」系のトラブルにハマった経験はありますか🤔?
モノレポや複数プロジェクト構成でのハマりどころ、便利な診断方法などがあれば、ぜひコメントで教えてください🙌
開発環境のパフォーマンスTipsやハマりポイントも共有いただけると嬉しいです🌏!