Reactのnode_modulesが巨大|容量問題の理解と対処法

Reactプロジェクトのnode_modulesが巨大になる原因と対処法を解説。依存関係の最適化、パッケージ選択、ビルドサイズ削減まで実践的に説明します。

Learning Next 運営
17 分で読めます

みなさん、Reactプロジェクトのnode_modulesが巨大になって困ったことはありませんか?

「なんでこんなに重いの?」「ディスク容量が足りない!」と悩んだ経験はありませんか?

この記事では、node_modulesが重くなる原因と効果的な対処法を詳しく解説します。 難しそうに見えますが、実は簡単にできる方法がたくさんあるんです。

一緒にnode_modulesをスッキリ軽量化して、快適な開発環境を作りましょう!

なぜnode_modulesはこんなに重いの?

依存関係の連鎖が原因

簡単に言うと、1つのパッケージが他のパッケージをたくさん呼び出しているからです。

例えば、Material-UIを1つインストールしただけで、実際には何百ものパッケージが一緒にダウンロードされます。

npm install @mui/material

# 実際にインストールされるパッケージ(一部)
@babel/runtime              # 2.3MB
@emotion/react             # 1.8MB
@emotion/styled            # 1.2MB
@types/react               # 800KB
# ...さらに数十の依存関係

1つインストールしただけで、合計30MB以上になることもあります。 これが積み重なると、あっという間にギガバイト単位になってしまうんです。

同じパッケージが複数インストールされる

さらに問題なのが、同じ機能のパッケージが何度もインストールされることです。

node_modules/
├── packageA/
│   └── node_modules/
│       └── lodash@4.17.20/  # 1.5MB
├── packageB/
│   └── node_modules/
│       └── lodash@4.17.19/  # 1.5MB
└── lodash@4.17.21/          # 1.5MB (最新版)

# 結果:同じ機能なのに4.5MB!

バージョンが少し違うだけで、同じようなライブラリが何個も保存されてしまいます。

重いライブラリを選んでしまう

知らず知らずのうちに、重いライブラリを選んでしまうことも原因の一つです。

// 実際のパッケージサイズ比較
const packageSizes = {
  // 日付処理
  'moment': '2.5MB',     // 重い
  'date-fns': '800KB',   // 軽い(同じ機能)
  'dayjs': '300KB',      // 超軽い
  
  // HTTP クライアント
  'axios': '1MB',        // 重い
  'fetch': '0KB',        // ブラウザ内蔵(無料)
  
  // ユーティリティ
  'lodash全体': '1.5MB', // 重い
  'lodash個別': '50KB',  // 軽い(必要な分だけ)
};

同じ機能でも、選ぶライブラリによって10倍以上の差があることも珍しくありません。

まずは現状を把握してみよう

node_modulesのサイズを確認

現在のnode_modulesがどのくらい重いか確認してみましょう。

# node_modules の容量確認
du -sh node_modules/
# 出力例: 1.2G	node_modules/

# フォルダ別の詳細分析
du -sh node_modules/*/ | sort -hr | head -10

このコマンドを実行すると、どのパッケージが一番重いかがわかります。

# 出力例:
85M	node_modules/@babel/
45M	node_modules/@types/
38M	node_modules/typescript/
25M	node_modules/@mui/
18M	node_modules/webpack/

意外なパッケージが重いことがあるので、まずは確認してみてください。

未使用パッケージの検出

次に、実際には使っていないパッケージがないかチェックしましょう。

# 未使用パッケージの検出
npx depcheck

# 出力例
Unused dependencies
* @types/lodash
* moment
* jquery
* underscore

使っていないパッケージが見つかったら、すぐに削除できます。

# 自動削除
npm uninstall @types/lodash moment jquery underscore

これだけでも数十MBの削減になることがあります。

依存関係を最適化しよう

軽量な代替パッケージに変更

重いパッケージを軽い代替パッケージに変更するだけで、大幅な軽量化ができます。

日付処理ライブラリの変更

// ❌ moment.js (2.5MB)
import moment from 'moment';
const formatted = moment().format('YYYY-MM-DD');

// ✅ date-fns (800KB, 軽量!)
import { format } from 'date-fns';
const formatted = format(new Date(), 'yyyy-MM-dd');

同じ機能なのに、サイズは約3分の1になります。

ユーティリティライブラリの最適化

// ❌ lodash 全体 (1.5MB)
import _ from 'lodash';
const result = _.get(object, 'path.to.value', defaultValue);

// ✅ 個別インポート (50KB)
import get from 'lodash/get';
const result = get(object, 'path.to.value', defaultValue);

// ✅ さらに軽量: JavaScriptの標準機能
const result = object?.path?.to?.value ?? defaultValue;

最新のJavaScriptなら、ライブラリを使わなくても同じことができる場合が多いです。

HTTP クライアントの見直し

// ❌ axios (1MB)
import axios from 'axios';
const response = await axios.get('/api/data');

// ✅ fetch (ブラウザ内蔵)
const response = await fetch('/api/data');
const data = await response.json();

fetchはブラウザに標準搭載されているので、追加のパッケージは不要です。

package.jsonの整理

package.jsonを見直して、本当に必要なパッケージだけを残しましょう。

{
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    
    // ✅ 軽量代替に変更
    "date-fns": "^2.29.3",           // moment より軽量
    "clsx": "^1.2.1",                // classnames より軽量
    "zustand": "^4.4.1"              // Redux より軽量
  },
  "devDependencies": {
    // 開発時のみ必要なものはここに
    "@types/react": "^18.0.21",
    "typescript": "^4.8.4",
    "eslint": "^8.25.0"
  }
}

開発時にだけ必要なパッケージは、必ずdevDependenciesに入れましょう。

パッケージマネージャーを活用しよう

pnpmで劇的な容量削減

pnpmは、同じパッケージを複数の場所にコピーしない賢いパッケージマネージャーです。

# pnpm のインストール
npm install -g pnpm

# プロジェクトで使用
pnpm install

通常のnpmだと、プロジェクトごとに同じパッケージをコピーします:

# npm/yarn の場合
project-a/node_modules/react/    # 2MB
project-b/node_modules/react/    # 2MB (重複)
project-c/node_modules/react/    # 2MB (重複)
# 総計: 6MB

pnpmなら、1つのパッケージを複数のプロジェクトで共有します:

# pnpm の場合
~/.pnpm-store/react@18.2.0/      # 2MB (実体)
project-a/node_modules/react/    # リンク
project-b/node_modules/react/    # リンク  
project-c/node_modules/react/    # リンク
# 総計: 2MB (70%削減!)

pnpmの設定

# .npmrc - pnpm設定ファイル
store-dir=~/.pnpm-store
auto-install-peers=true
package-import-method=hardlink

この設定により、さらに効率的にパッケージを管理できます。

npm workspacesでモノレポ最適化

複数のプロジェクトがある場合は、workspacesが効果的です。

// package.json - ルートワークスペース
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}

これにより、共通の依存関係を1箇所にまとめて管理できます。

# 従来の複数プロジェクト
project-web/node_modules/     # 500MB
project-api/node_modules/     # 300MB  
project-shared/node_modules/  # 200MB
# 総計: 1GB

# workspaces 使用時
monorepo/
├── node_modules/            # 400MB (共有)
├── packages/shared/
├── apps/web/
└── apps/api/
# 総計: 400MB (60%削減)

ビルドサイズも最適化しよう

node_modulesだけでなく、実際にユーザーに配信されるファイルサイズも重要です。

Bundle Analyzerで分析

まず、どのライブラリが最終的なファイルサイズに影響しているか確認しましょう。

# バンドル分析ツールのインストール
npm install --save-dev webpack-bundle-analyzer

# Create React App での分析
npm install --save-dev source-map-explorer
npm run build
npx source-map-explorer 'build/static/js/*.js'

この分析により、実際にユーザーがダウンロードするファイルの内訳がわかります。

Tree Shakingの活用

Tree Shakingとは、使っていない部分を自動的に削除する機能です。

// ❌ 全体インポート(Tree Shakingが効かない)
import * as lodash from 'lodash';
import MaterialUI from '@mui/material';

// ✅ 個別インポート(Tree Shakingが効く)
import { debounce, throttle } from 'lodash';
import { Button, TextField } from '@mui/material';

// ✅ さらに細かい個別インポート
import debounce from 'lodash/debounce';
import Button from '@mui/material/Button';

個別インポートにするだけで、最終的なファイルサイズが大幅に削減されます。

Code Splittingで分割読み込み

すべてのコードを一度に読み込まず、必要な時だけ読み込むようにしましょう。

import { lazy, Suspense } from 'react';

// 遅延読み込み
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));

function App() {
  return (
    <Suspense fallback={<div>読み込み中...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/profile" element={<Profile />} />
      </Routes>
    </Suspense>
  );
}

ページごとに分割することで、初回読み込み時間を大幅に短縮できます。

継続的な管理で問題を防ぐ

自動チェックの設定

依存関係の問題を早期発見するため、自動チェックを設定しましょう。

// package.json scripts での自動化
{
  "scripts": {
    "analyze": "npx cost-of-modules",
    "check-unused": "npx depcheck",
    "optimize": "npm run check-unused && npm run analyze",
    
    // コミット前の自動チェック
    "precommit": "npm run check-unused"
  }
}

これにより、不要なパッケージをコミットする前に気づけます。

定期的な見直し

月に1回程度、依存関係を見直す習慣をつけましょう。

# 定期チェックのコマンド
npm run analyze      # 容量分析
npm run check-unused # 未使用検出
npm outdated         # 古いパッケージ確認

こまめにチェックすることで、問題が大きくなる前に対処できます。

実際の改善例

これらの最適化により、どの程度の改善が期待できるかご紹介します。

Before/After 比較

// 最適化前
const beforeOptimization = {
  nodeModulesSize: '2.5GB',
  installTime: '45秒',
  buildTime: '120秒',
  bundleSize: '850KB'
};

// 最適化後
const afterOptimization = {
  nodeModulesSize: '400MB',    // 84%削減
  installTime: '12秒',         // 73%削減
  buildTime: '45秒',           // 63%削減
  bundleSize: '350KB'          // 59%削減
};

使用した技術

この改善で使った主な技術は以下の通りです:

  • pnpmへの移行
  • 軽量代替パッケージへの変更
  • 未使用パッケージの削除
  • Tree Shakingの最適化
  • Code Splittingの実装

どれも特別難しいことではなく、少しずつ実践できるものばかりです。

まとめ

Reactのnode_modules容量問題は、適切な知識と継続的な管理で解決できます。

すぐにできることを実践してみましょう:

# 1. 現状分析(5分)
du -sh node_modules/
npx cost-of-modules

# 2. 未使用パッケージ削除(10分)
npx depcheck
npm uninstall [未使用パッケージ]

# 3. 軽量代替への移行(30分)
npm uninstall moment
npm install date-fns

中長期的な改善も計画的に進めましょう:

  • pnpmへの移行
  • Tree Shakingの最適化
  • 自動チェックの設定
  • 定期的な見直し習慣

これらの対策により、開発効率が大幅に向上します。 ディスク容量の問題も解決し、ビルド時間も短縮されます。

重要なのは継続的な管理です。 月に1回程度の定期チェックで、軽量で快適な開発環境を維持しましょう。

みなさんの開発がより快適になることを願っています!

関連記事