Every JavaScript project starts the same way: npm install. You wait. Your disk fills up. You check node_modules and find the same copy of lodash duplicated across twenty projects. You wonder if there's a better way. There is. It's called pnpm.

pnpm stands for performant npm. It's a drop-in replacement for npm that fundamentally rethinks how packages are stored on disk. The result is faster installs, dramatically less disk usage, and a stricter dependency model that catches bugs npm silently ignores. Once you switch, you don't go back.

The problem with node_modules

npm and Yarn both use a flat node_modules structure. When you install a package, it and all its dependencies get hoisted to the top of node_modules. This means your code can require() packages you never declared in your package.json. They're just there, accidentally accessible because some other dependency pulled them in.

These are called phantom dependencies. Your code works on your machine because the package happens to be in node_modules. But if that transitive dependency gets removed in an update, your code breaks — and the error gives you no clue why, because you never explicitly installed the thing that's now missing.

The second problem is disk waste. Every project gets its own full copy of every package. If you have ten React projects, you have ten copies of React, ten copies of TypeScript, ten copies of every shared dependency. On a typical machine with a few dozen projects, node_modules can eat 20-50 GB easily.

The content-addressable store

pnpm's core innovation is a global content-addressable store. Instead of copying packages into each project, pnpm stores every version of every package once in a central location (usually ~/.local/share/pnpm/store). Each project's node_modules contains hard links pointing back to that store.

Hard links are a filesystem-level feature. They don't copy data — they create another pointer to the same bytes on disk. The result: installing a package you've already used in another project is nearly instant and costs zero additional disk space.

The numbers are real. A benchmark with 100 projects sharing common dependencies showed npm using 12 GB while pnpm used 3.6 GB for the same packages. That's a 70% reduction. On CI servers where you're paying for disk and build time, this adds up fast.

  • First install — pnpm downloads packages to the global store and creates hard links. Comparable speed to npm.
  • Subsequent installs — packages already in the store are linked instantly. No network requests, no decompression.
  • Across projects — a new project that depends on React doesn't download React again. It links to the copy already in the store.

Speed

pnpm is consistently the fastest package manager in benchmarks. In the January 2026 benchmarks on pnpm.io, pnpm completed cold installs in 28.6 seconds compared to npm's 134.2 seconds and Yarn's 52.3 seconds. Warm installs (with a populated store) are even more dramatic — often completing in under 5 seconds where npm takes 30+.

The speed comes from three things working together:

  • Content-addressable store — most packages are already on disk. No download, no decompress.
  • Hard links instead of copies — creating a hard link is a single filesystem operation, orders of magnitude faster than copying files.
  • Parallel resolution — pnpm resolves, fetches, and links packages in parallel rather than sequentially.

For CI pipelines, the speed difference is meaningful. If your CI runs npm install 50 times a day and each takes 2 minutes, switching to pnpm could save you an hour of build time daily. At scale, that's real money.

Strictness that saves you

pnpm creates a non-flat node_modules structure using symlinks. Each package can only access the dependencies it explicitly declares. If package A depends on package B but doesn't declare it in its package.json, the import fails immediately — instead of silently working because B happened to be hoisted.

This is pnpm's most controversial feature and its most valuable one. It means your package.json is always the source of truth. If it works with pnpm, it works everywhere. If your package.json is wrong, pnpm tells you now instead of letting it become a production bug three months later.

The strictness also prevents dependency confusion attacks. Since packages can only access what they declare, a malicious package can't reach into your project's other dependencies. The attack surface is smaller by design.

  • No phantom dependencies — if it's not in your package.json, you can't import it.
  • No peer dependency ambiguity — pnpm resolves peer dependencies strictly and warns you about mismatches.
  • Lockfile integrity — pnpm-lock.yaml captures the exact dependency tree, including which packages can access which dependencies.

Monorepo support

pnpm has first-class workspace support built in, no additional tooling required. You define a pnpm-workspace.yaml file at the root of your repo, and pnpm handles cross-package linking, installation, and script execution across all workspace packages.

The workspace: protocol lets packages depend on each other using their local versions rather than pulling from the registry. Combined with pnpm's strict isolation, this means workspace packages can't accidentally use dependencies from sibling packages they haven't declared.

The --filter flag is the real power tool. It lets you run commands against specific packages, packages matching a pattern, or packages that changed since a specific git commit:

  • pnpm --filter @myorg/api build — build one specific package.
  • pnpm --filter "./packages/**" test — test all packages in a directory.
  • pnpm --filter "...[HEAD~1]" build — only build packages that changed in the last commit.

For large monorepos, pnpm's combination of disk efficiency (shared store across all workspace packages), strict isolation (no accidental cross-package dependencies), and smart filtering makes it the strongest option available. Companies running monorepos with 50+ packages consistently report that pnpm handles the scale better than npm or Yarn workspaces.

Migrating from npm

Switching to pnpm is straightforward. The CLI is intentionally similar to npm:

  • npm install becomes pnpm install
  • npm add react becomes pnpm add react
  • npm run build becomes pnpm build (you can drop run)
  • npx create-react-app becomes pnpm dlx create-react-app

The migration process: install pnpm globally (npm install -g pnpm or corepack enable pnpm), delete your existing node_modules and package-lock.json, then run pnpm install. pnpm generates its own pnpm-lock.yaml lockfile. That's it.

The one friction point: pnpm's strictness may surface phantom dependencies your project was unknowingly relying on. You'll see import errors for packages you never explicitly installed. The fix is simple — add them to your package.json. This is pnpm catching real bugs in your dependency declarations that npm was hiding.

For CI, replace npm ci with pnpm install --frozen-lockfile. For GitHub Actions, use pnpm/action-setup@v4 to install pnpm before your workflow runs. Every major CI system supports pnpm natively at this point.

How it compares

FeaturenpmYarnpnpm
Disk usageHigh (full copies)High (full copies)
Install speed (cold)~134s~52s
Install speed (warm)~30s~15s
Phantom dependenciesAllowed silentlyAllowed silently
Monorepo supportWorkspaces (basic)Workspaces
node_modules structureFlat (hoisted)Flat (hoisted) or PnP
Lockfilepackage-lock.jsonyarn.lock
Global storeNoNo
Corepack supportYesYes

The verdict

pnpm is not a marginal improvement over npm. It's a fundamentally better architecture for package management. The content-addressable store eliminates disk waste. The symlinked node_modules eliminates phantom dependencies. The speed improvements are measured in minutes, not seconds.

The argument against pnpm used to be ecosystem compatibility — some tools assumed a flat node_modules layout and broke with symlinks. That era is over. Every major framework, build tool, and CI system works with pnpm in 2026. React, Next.js, Vite, Turborepo, Nx, GitHub Actions, GitLab CI, Vercel, Netlify — they all support pnpm out of the box.

If you're starting a new project, use pnpm. If you're maintaining an existing project that uses npm, switching takes 5 minutes and will likely surface hidden dependency bugs you'll want to fix anyway. If you're running a monorepo, pnpm's workspace features and strict isolation are the best in the ecosystem.

The JavaScript ecosystem has enough chaos. Your package manager shouldn't add to it. pnpm brings correctness, speed, and efficiency — and it does it without requiring you to change how you think about dependencies. It just makes the whole thing work better.