Most JavaScript projects run on npm or yarn out of habit. pnpm solves three real problems those tools don't. Once you understand the mechanics, switching is obvious.
The Disk Problem
When you run npm install in two projects that both depend on React 19, npm copies React into node_modules twice. Do this across 20 projects and you have 20 copies of the same files sitting on your disk.
pnpm uses a content-addressable store at ~/.pnpm-store. Every package version is stored exactly once, keyed by its content hash. When a project needs React 19, pnpm creates a hard link from the store into the project's node_modules, not a copy.
Hard links point to the same inode on disk. The file exists once. Every project that uses it just gets another pointer to the same bytes.
The result: a typical development machine with a dozen Node.js projects saves gigabytes. New installs of already-cached packages are nearly instant because nothing is copied.
The Speed Problem
pnpm's speed comes from a few places:
Hard links are free. Creating a hard link is a metadata operation. The OS doesn't move any data. Contrast this with npm, which reads and writes every file on each install.
Parallel installation. pnpm fetches, resolves, and links in parallel stages rather than sequentially per package.
Better cache reuse. Because packages are stored by content hash, pnpm knows immediately whether a package is already available locally. No network request needed.
On a warm cache, pnpm install in a large monorepo takes seconds. The equivalent npm install takes minutes.
The Supply Chain Problem
This is the most important one.
npm uses flat node_modules. When package A depends on lodash, npm hoists lodash to the top level of node_modules. Your code can then require('lodash') even though you never listed it as a dependency.
These are called phantom dependencies: packages you use but don't own. They create two problems:
Reliability: If package A drops lodash from its dependencies in the next version, your code breaks even though you never touched it.
Security: A malicious package hoisted into your flat
node_modulesis accessible to your code. You might use it without knowing it came from a transitive dependency you didn't vet.
pnpm uses a non-flat, isolated node_modules structure. Under the hood, each package gets its own node_modules containing only what it explicitly declared:
node_modules/
.pnpm/
react@19.0.0/node_modules/react/
lodash@4.17.21/node_modules/lodash/
react -> .pnpm/react@19.0.0/node_modules/reactYour project code can only access packages listed in your own package.json. Trying to import an undeclared package throws an error at resolve time, not silently succeeding because something hoisted it.
This means a compromised transitive dependency cannot be accidentally imported by your application code. The attack surface shrinks significantly.
Lockfile Integrity
pnpm's pnpm-lock.yaml includes content hashes for every package. Combined with --frozen-lockfile in CI:
pnpm install --frozen-lockfileThis refuses to install if the lockfile doesn't match package.json exactly, and verifies every package against its recorded hash. A tampered package or a registry substitution attack will fail loudly.
Delaying Updates with minimumReleaseAge
pnpm locks down what you can import. But packages you do declare still come from a registry you don't control. That's where most supply chain attacks land. A legitimate package gets compromised, a malicious version is published, and your next pnpm install pulls it in before anyone notices.
The key observation: most compromised packages are caught and yanked within hours of publication. The window between "malicious version published" and "community reports it" is short. You can exploit that window by simply not installing brand-new versions.
Since pnpm v10.16.0, this is built directly into pnpm-workspace.yaml as minimumReleaseAge, a number in minutes:
# pnpm-workspace.yaml
minimumReleaseAge: 1440 # 24 hoursWith this set, pnpm refuses to install any package version published less than 1440 minutes ago, including transitive dependencies. A malicious version published this morning won't touch your node_modules until tomorrow at the earliest.
For most teams, 1440 minutes (one day) is the right balance. You can go higher for stricter environments:
minimumReleaseAge: 10080 # 7 daysIf you need to exempt specific packages (internal tooling you control, or packages where you need updates immediately):
minimumReleaseAge: 1440
minimumReleaseAgeExclude:
- '@myorg/*'
- viteThis is a nearly free security win. You're almost never in a situation where you genuinely need a dependency update within 24 hours. The tradeoff is negligible; the protection is real.
Blocking Postinstall Scripts
npm runs postinstall scripts automatically on every package install. This is how packages like esbuild or puppeteer compile native binaries, but it's also how a compromised package can execute arbitrary code on your machine the moment you run npm install.
pnpm v10 disables postinstall scripts by default. No package runs code at install time unless you explicitly allow it.
You opt in per-package in package.json:
{
"pnpm": {
"onlyBuiltDependencies": ["esbuild", "sharp", "@swc/core"]
}
}Only the packages listed here are permitted to run build scripts. Everything else installs silently. A compromised transitive dependency that injects a postinstall hook cannot execute. It simply won't run.
The migration cost is low. Most projects have a handful of packages that genuinely need build scripts (native bindings, binary downloads). Audit your node_modules once, add them to the allowlist, and you're done.
Switching
npm install -g pnpmIn an existing project, delete node_modules and package-lock.json, then:
pnpm import # converts package-lock.json or yarn.lock to pnpm-lock.yaml
pnpm installThe package.json doesn't change. Scripts, CI commands, and editor integrations all keep working. The only thing that changes is what happens inside node_modules.
The flat node_modules approach npm popularised was a pragmatic compromise from 2010. pnpm removes that compromise without breaking the interface. If you're starting a new project today, there's no reason not to use it.
Further reading: pnpm Supply Chain Security