npm vs yarn vs pnpm

intermediate npm yarn pnpm package-manager lockfile

All three install packages from the npm registry. They differ in how they install — disk layout, speed, strictness, and the lockfile format. Picking one matters more than people think.

The big idea behind each

  • npm — the original. Ships with Node. Uses a hoisted, flat node_modules. Lockfile: package-lock.json.
  • yarn — Facebook’s reaction to slow npm. Classic v1 uses hoisted layout like npm. Modern Yarn (Berry, v2+) uses Plug’n’Play (PnP) — no node_modules at all. Lockfile: yarn.lock.
  • pnpm — performant npm. Uses a global content-addressable store + hardlinks + a nested-but-symlinked node_modules. Lockfile: pnpm-lock.yaml.

The install layout difference

This is the key bit. Same package.json → very different folders.

npm / yarn classic — hoisted
node_modules/
├── express/
├── lodash/         ← hoisted up
├── debug/          ← hoisted up
└── pg/
    └── node_modules/
        └── pg-types/
Anything in node_modules root is requirable, even if not in package.json (phantom deps).
pnpm — content-addressable store
~/.pnpm-store/    (global, hashed files)
node_modules/
├── express → .pnpm/express@4.18/...
├── pg → .pnpm/pg@8.11/...
└── .pnpm/
    ├── express@4.18.0/node_modules/express/
    └── lodash@4.17.21/node_modules/lodash/
Only direct deps are at root. Strict — no phantom deps. Files are hardlinks to the global store.

Why pnpm is so much faster (and uses less disk)

pnpm keeps one copy of each package version in a global store (~/.pnpm-store). When we install, it creates hardlinks from node_modules to that store. Hardlinks share the same disk blocks — basically zero copy.

So 50 projects all using react@18.2.0 share one copy on disk. With npm, each project has its own full copy. On a dev laptop, this saves tens of GB.

# Install in current project
pnpm install

# See the store size
pnpm store path
# /Users/me/Library/pnpm/store/v3

Strictness — phantom dependencies

This is where pnpm beats npm in code correctness. With npm’s flat layout, our code can require("debug") even if we never listed debug in our package.json — because some transitive dependency installed it and hoisting flattened it to the top.

// works with npm if any dep depends on lodash, even if WE don't:
const _ = require("lodash"); // phantom dep!

The day that transitive package upgrades and drops lodash, our code breaks. pnpm’s symlink-based layout makes this impossible — we can only require what we explicitly declared.

Lockfiles — three formats, same purpose

A lockfile records the exact version of every package (direct and transitive) so we get the same install on every machine, every time.

# npm
package-lock.json     # JSON, very verbose

# yarn
yarn.lock             # custom format, more compact

# pnpm
pnpm-lock.yaml        # YAML

Always commit the lockfile. Without it, CI may install different transitive versions than dev — leading to “works on my machine” bugs.

For CI we use the strict install variants which fail if lockfile and package.json disagree:

npm ci             # strict install, deletes node_modules first
yarn install --immutable
pnpm install --frozen-lockfile

Common commands side by side

action
npm
yarn
pnpm
install all
npm install
yarn
pnpm install
add dep
npm i pkg
yarn add pkg
pnpm add pkg
add dev
npm i -D pkg
yarn add -D pkg
pnpm add -D pkg
remove
npm rm pkg
yarn remove pkg
pnpm rm pkg
run script
npm run x
yarn x
pnpm x
CI strict
npm ci
--immutable
--frozen-lockfile

Workspaces / monorepos

All three support workspaces — multiple packages in one repo.

// package.json at root
{
  "workspaces": ["packages/*", "apps/*"]
}

pnpm uses pnpm-workspace.yaml instead:

packages:
  - 'packages/*'
  - 'apps/*'

For monorepos, pnpm is the most popular choice in 2026 because of strictness and speed. Yarn Berry workspaces are powerful but the PnP layout breaks some tools.

Which one should we use?

  • pnpm — best default for new projects. Fast, disk-efficient, strict. The whole frontend ecosystem (Vue, Vite, Astro) uses it.
  • npm — fine for small projects. Pre-installed everywhere. Zero setup.
  • yarn — still solid for legacy projects on Yarn 1. Yarn Berry’s PnP is interesting but the migration is real work.

Whichever we pick, stick with one per project and commit the lockfile.