caduh

npm vs Yarn vs pnpm — a practical guide to speed, disk usage, and lockfiles

4 min read

A no-nonsense comparison of npm, Yarn, and pnpm: real-world differences in install speed, node_modules size, and lockfile handling—plus when to pick which.

TL;DR

  • Speed: pnpm and modern Yarn (Berry) are usually fastest thanks to shared caches and smarter linking. npm has improved a lot (v7+) but tends to be slower on cold installs.
  • Disk: pnpm wins for large monorepos: a content‑addressable store + hard links → tiny node_modules. Yarn PnP can be smallest (no node_modules), but needs ecosystem compatibility.
  • Lockfiles: Commit them. npmpackage-lock.json, Yarn → yarn.lock, pnpmpnpm-lock.yaml. Use frozen/immutable installs in CI to guarantee determinism.
  • Choosing: Default to pnpm for big repos or when disk matters; Yarn (Berry) when you want PnP or high‑end workspace features; npm when you need zero extra tooling and broadest compatibility.

What actually differs (practical view)

1) Install speed

  • Cold installs: pnpm/Yarn reuse a global store and link files, avoiding duplicate extraction; npm extracts per project.
  • Re‑installs: pnpm/Yarn tend to be much faster because they link from cache; npm ci is faster than npm install for CI.
  • Monorepos: pnpm and Yarn workspaces install once for the whole repo; npm workspaces exist but are newer/less optimized.

2) Disk usage / node_modules

  • npm (hoisted layout): flattens dependencies; can produce huge node_modules with duplicates across projects.
  • pnpm (symlinked layout): stores one copy of each package/version in a content‑addressable store and links into projects → dramatically smaller disk footprint.
  • Yarn (Berry) Plug’n’Play (PnP): optionally skips node_modules entirely; a .pnp.cjs file maps imports. Smallest on disk but some tools still expect node_modules (you can switch Yarn to nodeLinker: node-modules).

3) Lockfile & determinism

  • Files: package-lock.json (npm), yarn.lock (Yarn), pnpm-lock.yaml (pnpm). Always commit these.
  • CI flags:
    • npm: npm ci (errors if lock and package.json disagree).
    • Yarn 1: yarn install --frozen-lockfile
    • Yarn Berry (2+/3+): yarn install --immutable
    • pnpm: pnpm install --frozen-lockfile
  • Integrity: All verify package integrity via checksums; pnpm’s lockfile is YAML and workspace‑aware by default.

Quick compare (day‑to‑day)

| Topic | npm (v9+) | Yarn (1.x / Berry) | pnpm (8+) | |---|---|---|---| | Speed | Solid, slower on cold install | Yarn 1 fast; Berry very fast | Fastest in most mono repos | | Disk use | Highest (duplicates) | PnP: smallest; node‑modules linker ≈ npm | Low via global store + links | | node_modules | Classic hoisting | PnP (no node_modules) or node-modules | Symlinked, strict by design | | Workspaces | ✅ (basic) | ✅ mature (Berry shines) | ✅ mature, great for mono repos | | Deterministic CI | npm ci | --frozen-lockfile / --immutable | --frozen-lockfile | | Strictness (phantom deps) | Permissive | PnP forbids; node‑modules looser | Forbids phantom deps | | Setup friction | None (bundled) | Needs Corepack/config for Berry | Needs Corepack or separate install | | Ecosystem compat | Maximum | Great; some tools need tweaks with PnP | Great; rare edge cases |

Phantom deps = importing a package you didn’t list in package.json but that happened to be hoisted. pnpm (and Yarn PnP) prevent this, helping you catch mistakes early.


Commands & flags you’ll actually use

Install & run

# npm
npm install         # or: npm ci (for CI)
npm run build

# Yarn (Berry recommended via Corepack)
corepack enable
yarn install        # or: yarn install --immutable
yarn build

# pnpm
corepack enable
pnpm install        # or: pnpm i --frozen-lockfile
pnpm build

Add/remove deps

# npm
npm i lodash         # --save-dev for dev deps
npm un lodash

# Yarn
yarn add lodash
yarn remove lodash

# pnpm
pnpm add lodash
pnpm remove lodash

Workspaces (monorepo root)

# npm (package.json: "workspaces": ["packages/*"])
npm -w packages/web add react

# Yarn (berry)
yarn workspace web add react

# pnpm (pnpm-workspace.yaml)
pnpm -F web add react

Deterministic CI install

npm ci
yarn install --immutable
pnpm install --frozen-lockfile

When to pick which

  • pnpm: large monorepos, disk pressure, strict dependency hygiene, fast installs.
  • Yarn (Berry): want PnP (no node_modules), very polished workspace DX, and strong constraints.
  • npm: smallest moving parts, no extra tooling, or constrained environments where only npm is available.

Pitfalls & quick fixes

| Problem | Why it bites | Fix | |---|---|---| | Mixed lockfiles (yarn.lock + package-lock.json) | Nondeterministic installs | Pick one manager; delete the other lockfiles | | “Works on my machine” via phantom deps | Hidden hoist behavior | Use pnpm or Yarn PnP; or run npm ls to detect | | Tools break under Yarn PnP | They assume node_modules | Set nodeLinker: node-modules or use pnpm | | CI slow with npm | Rebuilds from scratch | Use npm ci and a shared cache; consider pnpm | | Huge node_modules | Disk waste, slow editors | Switch to pnpm or Yarn PnP | | Global store growth (pnpm/yarn) | Cache bloat over time | Periodically pnpm store prune / yarn cache clean |


Quick checklist

  • [ ] Commit the lockfile; fail CI when it changes unexpectedly.
  • [ ] Use immutable/frozen installs in CI.
  • [ ] For monorepos, enable workspaces and a shared cache.
  • [ ] Guard against phantom deps (pnpm/Yarn PnP).
  • [ ] Periodically prune caches and audit (npm audit / pnpm audit / yarn npm audit).

One‑minute adoption plan

  1. Pick a manager and standardize (document + precommit check).
  2. Turn on workspaces and immutable/frozen CI installs.
  3. For big repos, trial pnpm on a branch; compare install time and disk use.
  4. If adopting Yarn Berry, decide between PnP vs node-modules linker and update tooling.
  5. Add cache pruning to housekeeping and watch metrics (CI time, node_modules size).