NodeJSYarn & pnpm Alternatives

Yarn & pnpm

npm is the default, but it is not the only package manager — and the alternatives exist for good reasons. Yarn pioneered lockfiles and workspaces; pnpm rethought node_modules entirely to save disk space and kill phantom dependencies. All three read package.json and talk to the same registry, so switching is mostly about the CLI and the node_modules layout.

The three managers

npm

Yarn

pnpm

Ships with Node

Yes

No

No

Lockfile

package-lock.json

yarn.lock

pnpm-lock.yaml

node_modules layout

Flat (hoisted)

Flat (hoisted)

Symlinked + content store

Disk usage

High (copies per project)

High

Low (one global store)

Phantom deps possible

Yes

Yes

No (strict by default)

Best known for

Ubiquity

Workspaces, PnP

Speed + disk efficiency

Command equivalents

The muscle memory transfers easily — the verbs are nearly identical:

Task

npm

Yarn

pnpm

Install all

npm install

yarn

pnpm install

Add a package

npm i pkg

yarn add pkg

pnpm add pkg

Add dev dep

npm i -D pkg

yarn add -D pkg

pnpm add -D pkg

Remove

npm uninstall pkg

yarn remove pkg

pnpm remove pkg

Run a script

npm run x

yarn x

pnpm x

Clean install

npm ci

yarn --immutable

pnpm i --frozen-lockfile

Run a binary once

npx tool

yarn dlx tool

pnpm dlx tool

How pnpm changes node_modules

pnpm's headline feature is its store. Every version of every package is stored once on disk, globally, and projects link to it. This is why pnpm is dramatically more disk-efficient:

pnpm's content-addressable layout

Text
~/.pnpm-store/          ← ONE global store, hard-linked into projects
   └─ lodash@4.17.21/   ← stored once, shared by every project on the machine

project/node_modules/
   ├─ .pnpm/            ← the real packages, hard-linked from the store
   └─ lodash → symlink into .pnpm/lodash@4.17.21
       (only your DIRECT deps are symlinked at the top level)
Why pnpm has no phantom dependencies
In npm's flat layout, a hoisted transitive package sits at the top of `node_modules`, so your code can `require` it even though you never installed it (a "phantom dependency"). pnpm only links your **direct** dependencies at the top level — transitive ones are tucked inside `.pnpm`. So importing a package you didn't declare *fails immediately*, catching the bug at dev time. See the phantom-dependency warning in [Installing Packages](/nodejs/installing-packages).
Yarn's two eras

"Yarn" means two quite different things, which trips people up:

Yarn Classic (1.x)

Yarn Modern (2+, "Berry")

node_modules

Yes (flat, like npm)

Optional — defaults to Plug'n'Play

Install model

Conventional

PnP: a single .pnp.cjs resolver map, no node_modules

Migration friction

Low

Higher — some tools assume node_modules exists

Yarn PnP trades compatibility for speed
Yarn Modern's Plug'n'Play removes `node_modules` entirely, resolving imports through a generated map. It is fast and strict, but tools that scan `node_modules` directly (some bundlers, older CLIs) may need a compatibility shim. If you adopt PnP, verify your whole toolchain supports it first.
Lock the manager with Corepack

Node ships Corepack, which pins which package manager (and version) a project uses, so teammates can't accidentally mix npm and pnpm in the same repo. Declare it with packageManager in package.json:

JSON
{ "packageManager": "pnpm@9.1.0" }

Bash
corepack enable          # let Corepack manage yarn/pnpm shims
# now "pnpm install" uses exactly 9.1.0, fetched on demand
Never commit two different lockfiles
The cardinal sin is a repo containing both `package-lock.json` and `pnpm-lock.yaml` — they will drift, and different developers will install different trees. Pick one manager per project, commit *its* lockfile, and `.gitignore` or delete the others. `packageManager` + Corepack enforces this.
Which should you use?
  • npm — the safe default. Zero setup, universal docs, fine for most projects.

  • pnpm — choose for monorepos, many projects on one machine, or when strictness and disk savings matter. Increasingly the power-user default.

  • Yarn — choose Classic for legacy projects already on it; Modern/PnP only if you want its specific model and have verified tooling support.

Next
Ready to share your own code with the world: [Publishing Packages](/nodejs/publishing-packages).