devnotes

pnpm vs npm workspaces: which should you use for a monorepo?

Use pnpm once your monorepo has more than a handful of packages; npm workspaces is the right call for a small repo where you'd rather not add another tool. The deciding factor isn't install speed, it's whether you want strict dependency isolation. pnpm gives every package only the dependencies it actually declared, so a package can't accidentally import something a sibling installed. npm workspaces hoists everything into one root node_modules, which is simpler until a phantom-dependency bug ships to production because a package used a library it never listed. pnpm also stores one copy of each package version in a global content-addressable store and hard-links it into each project, so a ten-package repo doesn't keep ten copies of React on disk. npm workspaces has shipped since npm v7 (October 2020) and covers the basics: shared install, symlinked local packages, one lockfile. It just stops there.

Where pnpm pulls ahead

Three things, in order of how often they actually matter:

What that looks like

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

# reference a local package by its workspace version
# package.json -> "dependencies": { "@acme/ui": "workspace:*" }

# run one package's build, plus everything downstream of it
pnpm --filter @acme/ui... build

When npm workspaces is the better choice

If your repo is two or three packages, everything is first-party, and you don't have a phantom-dependency problem, npm workspaces costs you nothing and adds no tool to onboard. It reads a workspaces array in the root package.json, links local packages, and shares one lockfile. Reach for pnpm when install time starts to hurt, when a hoisting bug bites, or when you want per-package task filtering. Not before.

Switching is cheap

Both resolve the same package.json semantics, so the move is mechanical: delete node_modules and package-lock.json, add a pnpm-workspace.yaml listing your package globs, replace "*" local versions with workspace:*, and run pnpm install. Budget an afternoon for a mid-size repo, mostly spent on scripts that assumed a hoisted node_modules layout.

rm -rf node_modules package-lock.json
# add pnpm-workspace.yaml, then:
pnpm install
pnpm --filter web dev

Notes from building tooling at fernforge. Mechanism details per pnpm.io; npm workspaces shipped in npm v7 (2020).