# npm vs Yarn vs pnpm 徹底比較：公式ドキュメントで読み解くパッケージマネージャ選定ガイド【2026年版】

> npm・Yarn・pnpmの違いを各公式ドキュメント（2026年6月時点）に基づいて正確に技術比較。node_modulesの構築方式、ファントム依存の遮断、ディスク効率、ロックファイル、モノレポ、そして供給チェーン攻撃に効く『依存スクリプト既定オフ』まで、設計思想から選定基準を体系化します。

- 公開日: 2026-06-25
- 著者: 友田 陽大
- タグ: パッケージマネージャ, モノレポ, フロントエンド, CI/CD, セキュリティ
- URL: https://tomodahinata.com/blog/npm-vs-yarn-vs-pnpm-package-manager-comparison-guide

## 要点

- 3者の違いは結局『node_modules をどう作るか』の一点に収束する——npm/Yarn Classic はホイスト平坦化、pnpm は内容アドレスストア＋シンボリックリンク、Yarn Modern は node_modules を作らない PnP
- ホイストは『宣言していない依存（ファントム依存）を import できてしまう』穴を生む。pnpm の非平坦構造と Yarn PnP の厳格解決はこれを構造的に遮断する
- pnpm はパッケージ実体をグローバルの内容アドレスストアにハードリンクするため、同一バージョンはディスク上に1回しか存在しない（npm は原則プロジェクトごとに実コピー）
- 2026年最大の差は供給チェーン安全性の既定値——pnpm は v10（2025年初頭）以降、Yarn は 4.14 以降、依存のライフサイクルスクリプトを既定で実行しない。npm は v11 系では既定で実行し、オプトイン化は v12（2026年夏予定）
- 新規は pnpm を既定に、最大互換・同梱ゼロ設定が要るなら npm、厳格境界や zero-installs を狙うなら Yarn Modern。どれを選んでも packageManager 固定とフリーズインストールは必須

---

「npm でいいのか、Yarn にすべきか、それとも pnpm か」——新規プロジェクトの最初の `git init` の直後、ほぼ必ず立ち止まる問いです。そして多くのチームが、これを**好み**や**過去の惰性**で決めています。

先に結論を述べます。**2026年に新規プロジェクトを始めるなら、既定は pnpm が合理的です。** ディスク効率・厳格な依存境界・モノレポの依存集中管理・そして供給チェーン安全性の既定値で、現時点では最もバランスが良い。ただしこれは「pnpm が正義」という話ではありません。**一部のツールが平坦な `node_modules` を前提にしている**なら npm、**究極の厳格さと zero-installs** を狙うなら Yarn Modern（PnP）——正解は要件で変わります。

重要なのは、3つの差を「速い・遅い」「流行り・廃れ」といった印象で語らないことです。**3者の違いは、突き詰めると『`node_modules` をどう構築するか』という一点の設計思想に収束します。** この一点を理解すれば、ディスク効率も、ファントム依存の安全性も、CIの速度も、ツール互換性も、すべて演繹的に説明できます。本記事はそれを、各ツールの**公式ドキュメント**に忠実に分解します。

> **この記事のルール**：仕様・既定値・設定キーは **npm 公式 (docs.npmjs.com)／Yarn 公式 (yarnpkg.com・classic.yarnpkg.com)／pnpm 公式 (pnpm.io)** の **2026年6月時点**の記述に基づきます。パッケージマネージャの既定値は**バージョンで変わります**（後述する「依存スクリプト既定オフ」は pnpm v10・Yarn 4.14・npm v12 と、それぞれ導入版が違います）。本番投入前に必ず使用バージョンの公式ドキュメントで確認してください。設定例にシークレットは一切含めません（環境変数前提・ハードコード厳禁）。

---

## 0. メンタルモデル：すべては「node_modules の作り方」で決まる

最初に、この記事を貫く軸を1つだけ固定します。

**3者の本質的な違い＝「`node_modules`（＝Node.js が実行時に依存を解決する場所）を、どう構築するか」。**

Node.js のモジュール解決は単純で、`require`/`import` のたびに**現在のディレクトリから上へ `node_modules` を辿る**だけです。パッケージマネージャの仕事は、この解決が正しく速く行われるよう `node_modules`（あるいはその代替）を準備すること。ここに3つの思想があります。

| 思想 | 代表 | `node_modules` の姿 |
| --- | --- | --- |
| **① ホイスト平坦化** | npm / Yarn Classic | 依存ツリーをできるだけ平坦に巻き上げ、トップレベルに全部置く。実体はプロジェクトごとにコピー |
| **② 内容アドレスストア＋シンボリックリンク** | pnpm | 実体はグローバルの単一ストアにハードリンク。`node_modules` は非平坦で、宣言した依存だけをシンボリックリンク |
| **③ node_modules を作らない（PnP）** | Yarn Modern | `node_modules` を生成せず、`.pnp.cjs` という解決マップと zip キャッシュで依存を供給 |

この①②③の選択が、以下のすべてを決めます。

- **ディスク使用量**（実体をコピーするか、共有するか）
- **ファントム依存の安全性**（宣言していない依存を import できてしまうか）
- **インストール速度**（コピー量とリンク戦略）
- **ツール互換性**（`node_modules` を物理的に期待するツールが動くか）

以降の章は、この軸に沿って各論を掘ります。

---

## 1. 結論先取り：全体比較マトリクス

細部に入る前に、全体像を1枚で示します。各セルは後続の章で根拠を述べます。

| 観点 | **npm** | **Yarn Modern (2–4)** | **pnpm** |
| --- | --- | --- | --- |
| 既定の `node_modules` 構築 | ホイスト平坦化 | PnP（`node_modules` なし）。`node-modules`/`pnpm` linker も選択可 | 非平坦・シンボリックリンク（`isolated`） |
| 実体の持ち方 | 原則プロジェクトごとに実コピー | zip 圧縮キャッシュ（`.yarn/cache`） | グローバル内容アドレスストアへハードリンク（1バージョン=ディスク1コピー） |
| ファントム依存の遮断 | ❌ 既定で素通り | ✅ PnP は厳格遮断 | ✅ 既定（`isolated`）で遮断 |
| ロックファイル | `package-lock.json` | `yarn.lock`（Modern は YAML） | `pnpm-lock.yaml` |
| フリーズインストール | `npm ci` | `yarn install --immutable` | `pnpm install --frozen-lockfile` |
| ワークスペース | ✅ `workspaces` | ✅ `workspaces` + 制約(constraints) | ✅ `pnpm-workspace.yaml` + **catalog** |
| 依存バージョン集中管理 | `overrides` | constraints / resolutions | **catalogs**（`catalog:`） |
| **依存スクリプト既定** | ⚠️ **実行する**（v11系。v12で opt-in 予定） | ✅ **既定オフ**（4.14+） | ✅ **既定オフ**（v10+） |
| 公開直後インストール抑止 | （標準設定なし） | `npmMinimalAgeGate`（4.12+, 既定1日） | `minimumReleaseAge`（v11 既定1日） |
| 現行メジャー（2026-06） | npm 11 系（Node 同梱） | Yarn 4 系 | pnpm 11 系（Node 22+） |
| 最大の強み | 標準・同梱・最大互換 | 厳格さ＋zero-installs＋拡張性 | ディスク効率＋厳格境界＋モノレポ |
| 主な弱み | 安全既定が後発・ディスク非効率 | PnP のツール互換に SDK が要る | シンボリックリンク非対応ツールでハマる稀ケース |

> **Yarn Classic（1.x）について**：現行の最新は 1.22 系で、公式は「**メンテナンスモード**（新機能なし・重大/セキュリティ修正のみ）」と位置づけています。新規採用の対象は実質 Yarn Modern（2+）です。本記事で単に「Yarn」と書く場合は Modern を指します。

---

## 2. インストールモデル深掘り：node_modules はこう作られる

### 2.1 npm：ホイストによる「最大限の平坦化」

npm は v3 以降、**できるだけ平坦な `node_modules`** を作ります。公式の表現では「依存は可能な限り高い階層に置く（maximally flat）」で、規則はこうです——「**ある祖先の `node_modules` に既に同一パッケージがあるなら、現在の階層には入れない**」。

ただし**バージョン衝突は平坦化できない**ため、必要に応じてネストが残ります。公式の例：

```
node_modules/
├── a/                      # a@1
├── b/                      # b は a@1 を使う（トップの a で足りる）
└── c/
    └── node_modules/
        └── a/              # c は a@2 を使う → ここだけネスト
```

`a@1` と `a@2` のように異なるバージョンが必要なときだけ、`c/node_modules/a` が掘られる。これが「最大限の平坦化（hoisting）」の実態です。Node の解決器が上へ辿る性質と合わさり、ネストした `c` からでもトップの依存に届きます。

### 2.2 pnpm：内容アドレスストア → 仮想ストア → シンボリックリンク

pnpm の構造は3段です。

1. **内容アドレスストア**：全パッケージの全ファイルを、ディスク上の単一の場所に1回だけ保存（macOS なら既定 `~/Library/pnpm/store`）。
2. **仮想ストア `node_modules/.pnpm`**：各パッケージはここに `<name>@<version>/node_modules/<name>` の形で展開され、**実体はストアからのハードリンク**。
3. **シンボリックリンク**：プロジェクト直下の `node_modules` には、**`package.json` に宣言した直接依存だけ**が `.pnpm` 内へのシンボリックリンクとして並ぶ。

公式ドキュメントの構造例（`foo` が `bar` に依存する場合）：

```
node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo      # 直接依存だけが直下に出る
└── .pnpm
    ├── bar@1.0.0/node_modules/bar -> <store>       # 実体はストアへのハードリンク
    └── foo@1.0.0/node_modules
        ├── foo -> <store>
        └── bar -> ../../bar@1.0.0/node_modules/bar # 推移依存は .pnpm 内で相対リンク
```

ポイントは「**直下に出るのは宣言した依存だけ**」という一点です。これが次章のファントム依存遮断に直結します。

### 2.3 Yarn Modern：node_modules を作らない（PnP）

Yarn Modern の既定 linker は **PnP（Plug'n'Play）** で、そもそも `node_modules` を生成しません。代わりに **`.pnp.cjs`** を生成します。これは公式いわく「**プロジェクトの依存ツリーの全情報を持ち、各パッケージがディスク上のどこにあるか、`require`/`import` をどう解決するかをツールに教える**」ローダです。依存の実体は `.yarn/cache` 内の **zip アーカイブ**として置かれ、ローダ経由で直接参照されます。

PnP の効能は厳格さです。公式は「**Yarn は全パッケージとその依存の一覧を持つため、解決時に勘定していない依存へのアクセスを防げる**」と明記します。＝ファントム依存が**構造的に不可能**になります。

なお、Yarn Modern は PnP を強制しません。`nodeLinker` 設定で切り替えられます（公式が安定とする3値）。

| `nodeLinker` | 挙動 |
| --- | --- |
| `pnp`（既定） | `node_modules` なし。最も厳格・高速 |
| `node-modules` | Classic/npm 風の平坦 `node_modules`。互換性最優先（ファントム依存遮断はなし） |
| `pnpm` | シンボリックリンク＋ハードリンクで内容アドレスストアを使う（pnpm 風） |

> **よくある誤記**：`nodeLinker: pnp-loose` は**存在しません**。厳格/緩和は別設定 **`pnpMode`**（`strict`＝既定 / `loose`）で制御します。

---

## 3. ファントム依存：ホイストが生む静かな地雷

ここが**実プロジェクトで最も静かに事故る**ポイントです。

### 3.1 何が起きるか

ホイスト平坦化（npm / Yarn Classic / Yarn の `node_modules` linker）では、**推移依存がトップレベルに巻き上がる**ため、Node の解決器がそれを見つけてしまいます。結果、**`package.json` に宣言していないパッケージを `import` できてしまう**。これがファントム依存（幽霊依存）です。

```ts
// package.json には "express" しか書いていない。
// しかし express は内部で "debug" に依存しており、debug がトップに巻き上がる。

import createDebug from "debug"; // ✅ 動いて「しまう」
const log = createDebug("app");
```

このコードは**今日は動きます**。しかし——

- express が将来 debug を別物に差し替えたら？ → ある日突然 `Cannot find module 'debug'`。
- 別の依存構成のマシン／CI で巻き上がり位置が変わったら？ → 「自分の環境では動くのに本番で壊れる」。

npm 公式はホイストと「解決器が上へ辿る」挙動を明記していますが、これを「ファントム依存」というリスクとして警告はしていません。**仕組みからの帰結**として広く知られた落とし穴です。

### 3.2 pnpm / Yarn PnP はこれを構造的に殺す

- **pnpm（既定 `isolated`）**：直下 `node_modules` に出るのは宣言した依存だけ。公式いわく「**本当に dependencies にあるパッケージだけがアクセス可能**」。未宣言の `import` は解決できず、開発時に落ちます。
- **Yarn PnP**：解決マップにない依存へのアクセスを、意味のあるエラーで弾く。

> **実務上の含意**：「ホイスト前提で長年回ってきた既存リポを pnpm / PnP へ移行すると、隠れていたファントム依存が一斉に露出する」——これは**バグではなく、これまで隠れていた負債の可視化**です。移行は「壊れた」のではなく「正しくなった」と捉え、露出した依存を `package.json` に正しく宣言していくのが筋です。

---

## 4. ディスク効率と速度：なぜ pnpm は「軽い」のか

### 4.1 実体の持ち方が根本的に違う

- **npm**：グローバルキャッシュ（`~/.npm` 配下の `_cacache`、内容アドレス方式で整合性検証付き）は持つものの、インストール時は**各プロジェクトの `node_modules` に実ファイルを展開**します。＝同じ `react@19` を 10 プロジェクトで使えば、原則 10 コピー（実験的な `linked` 戦略は例外）。
- **Yarn Modern**：依存を**内容アドレス方式の zip アーカイブ**としてグローバルキャッシュに保持。`node-modules` linker 使用時は `nmMode: hardlinks-global` でプロジェクト横断のハードリンク共有も可能。
- **pnpm**：**1つのバージョンはディスク上に1回だけ**。各プロジェクトの実体はストアへの**ハードリンク**なので「追加のディスクを消費しない」（公式）。さらに更新も差分的で、100 ファイル中 1 ファイルだけ変わったバージョンに上げても「ストアに増えるのは 1 ファイルだけ」。

数字は環境（OS・ファイルシステム・キャッシュ温度・依存規模）で大きく振れるため、本記事では**桁を断定しません**。重要なのは「**pnpm のディスク優位は偶然ではなく、内容アドレスストア＋ハードリンクという設計の必然**」という構造的な理由です。インストール速度も同様に、「実ファイルのコピー量を最小化し、リンクで済ませる」構造が効きます。

### 4.2 キャッシュの掃除

- pnpm：`pnpm store prune`（どのプロジェクトからも参照されない実体を除去。再取得されるため副作用なし）。
- npm：`npm cache verify`（整合性検証）／必要時のみ容量回収目的で `clean`。
- Yarn：グローバルキャッシュは `enableGlobalCache`（既定 true）で制御。

---

## 5. ロックファイルと再現性：CI を壊さない作法

| | npm | Yarn Modern | pnpm |
| --- | --- | --- | --- |
| ファイル名 | `package-lock.json` | `yarn.lock`（YAML） | `pnpm-lock.yaml`（YAML） |
| 整合性 | SHA-512 の SRI 文字列を記録 | 解決チェックサムを記録 | 解決を記録 |
| フリーズ | `npm ci`（ロック必須・`node_modules` を消して再構築・`package.json` と不一致なら**失敗**） | `yarn install --immutable`（ロック更新を禁止） | `pnpm install --frozen-lockfile` |

3者に共通する鉄則は2つです。

1. **ロックファイルは必ずコミットする**（3ツールとも公式が明記）。dev・CI・本番で同一ツリーを保証する単一真実源。
2. **CI ではフリーズインストールを使う**。`npm install` 相当の「ロックを更新し得る」コマンドを CI で使うのは、再現性を自ら捨てる行為です。CI は上表のフリーズ系を使い、ロックのドリフトを**赤信号で止める**。

> 私は決済基盤や受賞 B2B SaaS の構築で、「ローカルでは通るのに CI で落ちる／本番でだけ壊れる」の大半が**インストールの非決定性**に起因するのを見てきました。`npm ci` / `--immutable` / `--frozen-lockfile` を CI ゲートに据えるだけで、この種の事故は構造的に消せます。

---

## 6. モノレポ：workspaces と「依存の集中管理」

3者とも**ワークスペース（モノレポ）をネイティブ対応**します。`workspace:` プロトコル（社内パッケージをローカル参照し、公開時だけ実バージョンへ変換）も共通です。差が出るのは「**外部依存のバージョンをどう1か所に集めるか**」です。

| 機能 | npm | Yarn Modern | pnpm |
| --- | --- | --- | --- |
| ワークスペース定義 | `package.json` の `workspaces` | `package.json` の `workspaces` | `pnpm-workspace.yaml` |
| 横断実行 | `--workspaces` / `--workspace` | `yarn workspaces foreach`（並列・トポロジカル順） | `pnpm -r` / `--filter` |
| バージョン集中管理 | `overrides`（強制上書き） | constraints（JS で規約を宣言・`--fix` 可） | **catalogs**（`catalog:` で共有定数化） |
| 配布成果物の切り出し | （手動） | （手動 / プラグイン） | `pnpm deploy`（自己完結 `node_modules` を生成。Docker 向き） |

pnpm の **catalog** は、`zod` や `react` のような共有外部依存のバージョンを `pnpm-workspace.yaml` の1か所に定義し、各 `package.json` から `"react": "catalog:"` で参照する仕組みです。**バージョンのサイレントなドリフトを構造的に殺す**——モノレポで最も効く機能の1つです。

```yaml
# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"

catalog:
  react: ^19.2.0
  zod: ^3.25.0
```

```jsonc
// apps/web/package.json — バージョンは catalog に委譲（ここでは固定しない）
{
  "dependencies": {
    "react": "catalog:",
    "zod": "catalog:"
  }
}
```

モノレポを「育てられる資産」にする設計（catalog でドリフトを消し、Turborepo でキャッシュし、共有ドメイン型を単一真実源にする）は、別記事で実コードとともに掘り下げています：[pnpm + Turborepo で型安全なモノレポを設計する](/blog/pnpm-turborepo-monorepo-architecture-type-coverage-guide)。実案件（[サブスク学習プラットフォーム](/case-studies/subscription-learning-platform)の共有14パッケージ構成）の知見が土台です。

---

## 7. 供給チェーン安全性：2026年、最大の差はここ

ここが**2026年に最も実利のある差**であり、発注者・受注者ともに見るべき論点です。鍵は「**依存パッケージのライフサイクルスクリプト（`postinstall` 等）を、既定で実行するか**」。これは `npm install` しただけで第三者のコードが任意実行される、という供給チェーン攻撃の主要経路だからです。

### 7.1 依存スクリプトの既定値（バージョン厳守）

| ツール | 依存の install/postinstall を既定で… | 導入版 / オプトイン手段 |
| --- | --- | --- |
| **pnpm** | **実行しない** | **v10**（2025年初頭）〜。`onlyBuiltDependencies` / `pnpm approve-builds` で許可。v11 では `allowBuilds` に集約 |
| **Yarn Modern** | **実行しない** | **4.14** 〜。`enableScripts: true` で全体許可、`dependenciesMeta` で個別許可 |
| **npm** | **実行する** | v11 系は既定実行。**v12（2026年夏予定）でオプトインへ**。11.16+ で先取りの `npm approve-scripts` |

つまり 2026年6月時点で、**pnpm と Yarn は「既定で安全」、npm は「まだ既定で実行」**（v12 で追随見込み）。この差は、エンタープライズのセキュリティ要件に対する**既定のスタンス**の差として効いてきます。

```yaml
# pnpm-workspace.yaml — ビルドを許可する依存だけを明示（v10+ は既定でブロック）
onlyBuiltDependencies:
  - esbuild
  - "@swc/core"
# ↑ ここに無い依存の postinstall は実行されない。`pnpm approve-builds` で対話的に承認も可
```

```yaml
# .yarnrc.yml — Yarn 4.14+ は既定でスクリプト無効。必要な依存だけ個別に許可するのが筋
# enableScripts: false   # ← 既定。全体ONは avoid
```

```ini
# .npmrc — npm を今すぐ硬めるなら（注意：これは“自分のスクリプトも”止める）
# ignore-scripts=true
# → CI では `npm ci --ignore-scripts` 後、必要なビルドだけ明示実行するのが安全。
#   npm 11.16+ なら `npm approve-scripts` による選択的許可が望ましい。
```

### 7.2 公開直後の悪性リリースを踏まない「エイジゲート」

近年の攻撃は「正規パッケージを乗っ取り、悪性バージョンを**公開直後**に撒く」型が増えています。これに対し：

- **pnpm**：`minimumReleaseAge`（v11 既定 **1日**）——公開からN分経過していないバージョンはインストールしない。
- **Yarn**：`npmMinimalAgeGate`（4.12+, 既定 **1日**）——同趣旨。
- **npm**：標準の同等設定は無し（2026-06時点）。

### 7.3 整合性・来歴・監査

- **整合性**：npm はロックに SHA-512 の SRI、Yarn/pnpm はチェックサムを保持。Yarn の **hardened mode**（`enableHardenedMode`）はロック内容をレジストリと突き合わせて改ざんを検知し、**公開リポジトリからの PR で自動有効化**されます。
- **来歴（provenance）**：npm は sigstore による**来歴証明**（`npm publish --provenance`、GitHub Actions では `id-token: write` が必要）と `npm audit signatures` による検証を提供。
- **監査**：`npm audit` / `yarn npm audit` / pnpm（`pnpm audit`）。いずれもインストール時に自動実行はされないため、**CI の独立ジョブ**に組み込むのが定石です。

> 受発注の現場で「セキュリティはどうしていますか」と問われたとき、「**依存スクリプトを既定で止め、エイジゲートを効かせ、フリーズインストールと監査を CI ゲートにしている**」と即答できることは、そのまま信頼の差になります。私が4ラウンドのセキュリティ監査を経た B2B SaaS や、本番二重課金0件の決済基盤で徹底してきたのは、まさにこの「**安全を運用の注意深さではなく、ツールの既定と CI の赤信号で強制する**」設計です。

---

## 8. バージョン固定：packageManager と Corepack

「ローカルは pnpm 9、CI は pnpm 11」のようなツール自体のバージョン差も、再現性を壊す要因です。3者とも **`package.json` の `packageManager` フィールド**で固定でき、**Corepack** が読み取って自動的に該当バージョンを使います。

```jsonc
// package.json — チーム/CI で同一の PM バージョンを強制
{
  "packageManager": "pnpm@11.0.0"
}
```

```bash
corepack enable          # packageManager の宣言に従って pm を解決
```

> **注意（2026年）**：Corepack は Node 14.19〜24 系には同梱されていましたが、**Node 25 以降は同梱から外れました**。Node 25+ では `npm i -g corepack` で別途導入します。`packageManager` の宣言自体は引き続き有効です。

---

## 9. 選定フレームワーク：要件から逆算する

「速いから」「流行りだから」ではなく、**要件**から選びます。

### こういう要件なら → **pnpm**（新規の既定）

- ディスク効率を重視（CI ランナー・開発機・多数プロジェクト）。
- **ファントム依存を構造的に禁止**したい（厳格な依存境界）。
- モノレポで**依存バージョンを集中管理**したい（catalog）。
- **供給チェーン安全性を既定で**確保したい（スクリプト既定オフ＋エイジゲート）。

### こういう要件なら → **npm**

- **追加インストール不要・最大互換**が要る（Node 同梱。CI・教育・OSS 配布で摩擦最小）。
- 一部ツールが**平坦な `node_modules` を物理的に前提**にしている。
- チームの学習コストを最小化したい（最も枯れた標準）。

### こういう要件なら → **Yarn Modern**

- **究極の厳格さ**（PnP）と**zero-installs**（`.yarn/cache` をコミットし `yarn install` 自体を消す）を狙う。
- **constraints** で workspace 規約を宣言的に強制したい。
- PnP のために**エディタ SDK**（`yarn dlx @yarnpkg/sdks`）を導入する運用コストを許容できる。

### Yarn Classic（1.x）は？

**新規採用は非推奨**。公式がメンテナンスモードと位置づけており、既存リポの維持以外で選ぶ理由は乏しい。移行先は pnpm か Yarn Modern が現実的です。

> **どれを選んでも共通の必須事項**：`packageManager` でツール版を固定し、ロックファイルをコミットし、CI はフリーズインストール（`npm ci` / `yarn install --immutable` / `pnpm install --frozen-lockfile`）を使う。**1リポジトリに複数のロックファイルを混在させない**（事故の温床）。

---

## 10. 受注者・発注者が踏みやすい失敗（実務チェックリスト）

- ❌ **ロックファイルの混在**：`package-lock.json` と `pnpm-lock.yaml` が同居 → どちらが真実源か不定。1つに統一し、不要なものは削除。
- ❌ **CI で `npm install`**：ロックを更新し得るため非決定的。フリーズ系に統一する。
- ❌ **依存スクリプトを無防備に実行**：npm 既定や `enableScripts: true`/`dangerously-allow-all-builds` で全許可するのは供給チェーン的に危険。許可は**必要な依存だけ**に絞る。
- ❌ **`packageManager` 未固定**：ローカルと CI でツール版が割れ、再現性が崩れる。
- ❌ **pnpm/PnP 移行でのファントム依存放置**：露出した未宣言依存を「とりあえず `shamefully-hoist`／`node-modules` linker で隠す」のは負債の先送り。正しく `package.json` に宣言する。
- ❌ **`shamefully-hoist` の常用**：互換のための最後の手段であって、既定運用ではない。

---

## FAQ

**Q. npm はもう古いのですか？**
いいえ。npm は Node 同梱の標準で、最大互換という明確な強みがあります。安全既定（スクリプト opt-in）は後発ですが v12（2026年夏予定）で追随見込みです。「枯れた標準が要る」場面では今も第一候補になり得ます。

**Q. pnpm は本番で安全に使えますか？**
はい。多くの本番モノレポで使われ、むしろ**安全既定（v10 以降のスクリプト既定オフ、v11 のエイジゲート）とディスク効率**で先行しています。注意点はシンボリックリンク非対応の一部ツールでの稀なハマりで、その場合のみ `node-linker: hoisted` 等で対処します。

**Q. Yarn PnP はなぜ普及しきらないのですか？**
`node_modules` を物理的に前提とする一部ツールとの摩擦と、エディタ補完に SDK 導入が要る運用コストが理由です。厳格さと zero-installs の価値が運用コストを上回る組織で強くハマります。互換重視なら `nodeLinker: node-modules` も選べます。

**Q. 既存プロジェクトを乗り換えるべきですか？**
動いていて不満がなければ急ぐ必要はありません。乗り換えの最大の動機は「**ディスク/CI 効率**」「**ファントム依存の根絶**」「**安全既定**」。移行時はファントム依存の露出を**負債の可視化**と捉え、フリーズインストールを CI に必ず入れてから切り替えます。

**Q. 結局、最初の1つはどれにすべき？**
2026年の新規なら **pnpm を既定**に。最大互換が要件なら npm、厳格さ・zero-installs を積極的に取りに行くなら Yarn Modern。どれを選んでも `packageManager` 固定とフリーズインストールはセットで。

---

## まとめ

- 3者の違いは「**`node_modules` をどう作るか**」の一点に集約される——npm/Yarn Classic はホイスト平坦化、pnpm は内容アドレスストア＋シンボリックリンク、Yarn Modern は PnP。
- ホイストは**ファントム依存**という静かな地雷を生む。pnpm の非平坦構造と Yarn PnP の厳格解決はこれを構造的に遮断する。
- **ディスク効率**は pnpm の設計上の必然（1バージョン=ディスク1コピー）。
- **2026年最大の差は供給チェーン安全性の既定値**——pnpm（v10〜）と Yarn（4.14〜）は依存スクリプトを既定で止め、npm は v12 で追随見込み。
- ツールは要件で選ぶ。ただし**`packageManager` 固定・ロックのコミット・CI フリーズインストール**は、どれを選んでも共通の必須作法。

パッケージマネージャの選定は、見た目こそ小さな判断ですが、**ディスク・CI コスト、依存の安全境界、供給チェーンの既定スタンス**という、本番運用の土台に直結します。私は「一人 × 生成AI」で受賞 B2B SaaS や決済基盤を、**速く・安く・安全に**作ってきました。その土台には常に、こうした「**正しさを運用の善意ではなく、ツールの既定と CI の赤信号で強制する**」設計があります。技術選定や本番品質の作り込みでお困りの際は、お気軽にご相談ください。
