Every JavaScript project starts with npm install, yarn, or pnpm install. The package manager you choose affects install speed, disk usage, CI costs, and monorepo support.
Speed Benchmarks
Installing dependencies for a Next.js project (300+ packages):
| Operation | npm | Yarn (v4) | pnpm |
|---|---|---|---|
| Clean install (no cache) | 42s | 28s | 18s |
| Clean install (with cache) | 18s | 12s | 8s |
| Adding a package | 8s | 5s | 3s |
| Lockfile-only install (CI) | 25s | 15s | 10s |
Pnpm is 2-4x faster than npm and 30-50% faster than Yarn. The speed difference compounds in CI where installs happen on every commit.
Disk Usage
Installing the same project three times (simulating three projects with overlapping dependencies):
| Manager | Disk Usage | Approach |
|---|---|---|
| npm | 1.8 GB | Flat node_modules per project |
| Yarn (PnP) | 0.9 GB | Zip archives + PnP |
| pnpm | 0.7 GB | Content-addressable store + symlinks |
Pnpm uses a global content-addressable store. If 10 projects use React 19, one copy exists on disk with symlinks from each project. This saves gigabytes on developer machines.
Feature Comparison
| Feature | npm | Yarn (v4) | pnpm |
|---|---|---|---|
| Default with Node | Yes | No | No |
| Speed | Slowest | Fast | Fastest |
| Disk efficiency | Poor | Good (PnP) | Best |
| Monorepo workspaces | Yes | Yes (mature) | Yes (best) |
| Strict dependency resolution | No (flat) | Yes (PnP) | Yes (symlinked) |
| Lockfile format | package-lock.json | yarn.lock | pnpm-lock.yaml |
| Overrides/resolutions | overrides field | resolutions field | overrides + pnpm.overrides |
| Patch protocol | npm patch | yarn patch | pnpm patch |
| Script running | npm run | yarn run | pnpm run |
| Global installs | npm i -g | yarn global add | pnpm add -g |
| Side effects handling | No | No | Yes (side-effects cache) |
Monorepo Support
pnpm Workspaces
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
Pnpm workspaces are the most efficient. Shared dependencies are stored once in the content-addressable store. The --filter flag targets specific packages:
pnpm --filter web dev # Run dev in the web app
pnpm --filter ./packages/* build # Build all packages
Yarn Workspaces
Yarn introduced workspaces first and they are mature. Yarn 4's PnP (Plug'n'Play) mode eliminates node_modules entirely, replacing it with a single .pnp.cjs file.
npm Workspaces
npm added workspace support later. It works but lacks the filtering and efficiency features of pnpm and Yarn.
Strictness
npm (Flat node_modules)
npm hoists all dependencies to the top of node_modules. This means your code can accidentally import packages that are not in your package.json (they are there because a dependency of a dependency installed them). This creates "phantom dependencies" that break when hoisting changes.
pnpm (Symlinked)
pnpm creates a non-flat node_modules structure. Your code can only import packages explicitly listed in your package.json. Phantom dependencies are caught immediately. This is stricter and safer.
Yarn PnP
Yarn PnP removes node_modules entirely. Dependencies resolve through a map file. This is the strictest model and catches all phantom dependency issues, but some packages are incompatible with PnP.
CI Impact
For a monorepo with 100 packages, CI install times:
| Manager | Install Time | Cache Size |
|---|---|---|
| npm | 120s | 1.2 GB |
| Yarn (PnP) | 60s | 600 MB |
| pnpm | 45s | 400 MB |
Over 100 CI runs per day:
| Manager | Daily CI Time | Monthly Cost Impact |
|---|---|---|
| npm | 200 minutes saved | - |
| Yarn | 100 minutes | ~$50 less |
| pnpm | 75 minutes | ~$100 less |
Faster installs mean faster CI, which means faster deployments and lower CI costs.
Migration
From npm to pnpm
# Remove node_modules and lockfile
rm -rf node_modules package-lock.json
# Install pnpm
corepack enable && corepack prepare pnpm@latest --activate
# Install dependencies
pnpm install
Migration is usually painless. Most packages work without changes. The only issues arise from phantom dependencies that pnpm's strict mode catches.
From Yarn to pnpm
rm -rf node_modules .yarn yarn.lock
pnpm import # Converts yarn.lock to pnpm-lock.yaml
pnpm install
Our Choice
We use pnpm for all projects. The speed, disk efficiency, strict dependency resolution, and monorepo support make it the clear winner.
// package.json
{
"packageManager": "pnpm@9.15.0"
}
Corepack ensures every developer and CI environment uses the same pnpm version.
Contact us to discuss your development setup.