pnpm (performant npm) stores every package version once in a content-addressable global store and hard-links files into project node_modules. The on-disk layout is a non-flat, symlink-based structure that prevents accessing undeclared dependencies — the opposite of npm's permissive hoist.
That combination — disk efficiency + strictness + excellent workspace filtering — is why pnpm wins most large monorepos. See the comparison matrix on npm vs Yarn vs pnpm vs Bun.
📝
note
Install pnpm via Corepack and pin "packageManager": "pnpm@…" so every machine uses the same major.
Filesystem that supports hard links (local disk; some network FS break); Node + Corepack
When not?
Broken tools that need flat hoist and you cannot use shamefully-hoist / public-hoist-pattern
Content-Addressable Store
Each package tarball is unpacked into the store keyed by content hash. Projects never duplicate package bytes for the same version — they hard-link into node_modules/.pnpm and symlink package names into place.
store.sh
Bash
1
pnpm store path # where the global store lives
2
pnpm store status # verify integrity / modified packages
3
pnpm store prune # remove unreferenced packages
4
5
# Optional: relocate store (CI cache mounts)
6
pnpm config set store-dir /var/cache/pnpm-store
ℹ
info
In CI, cache the store directory keyed by lockfile hash. Combined with --frozen-lockfile, installs become mostly hard-link operations.
Hard Links & Layout
Hard links share inodes with the store. Editing a file under node_modules can mutate the store copy — avoid manual edits; use pnpm patch instead.
inspect-layout.sh
Bash
1
ls node_modules # only direct deps + .pnpm + .bin
2
ls node_modules/.pnpm | head # virtual store with versioned folders
3
readlink node_modules/lodash # symlink into .pnpm/.../node_modules/lodash
Path
Role
node_modules/.pnpm
Virtual store; real package files via hard links
node_modules/<name>
Symlink to declared dependency only
~/.local/share/pnpm/store
Typical global store location (OS-dependent)
Peer Dependency Strictness
pnpm is strict about peers. Missing or mismatched peer dependencies produce errors (not quiet hoisting). That forces correct package.json declarations — valuable in monorepos.
.npmrc
INI
1
# Default is strict; relax only when migrating legacy trees
2
auto-install-peers=true
3
strict-peer-dependencies=true
4
# Temporary escape during migration:
5
# strict-peer-dependencies=false
⚠
warning
Turning off strict peers hides real bugs. Prefer adding peers / using pnpm.peerDependencyRules for known false positives.
shamefully-hoist & Hoist Patterns
Some tools (older ESLint plugin resolvers, frameworks expecting flat trees) break under isolation. pnpm offers controlled hoisting escapes.