pnpm vs npm
The practical differences between pnpm and npm — how they install packages, manage disk space, handle lockfiles, and when to pick one over the other.
Recipe
Quick-reference recipe card — copy-paste ready.
# Install pnpm (requires Node.js 18.12+)
corepack enable
corepack prepare pnpm@latest --activate
# Or install via npm
npm install -g pnpm
# Verify
pnpm --version
# Common commands — side by side
npm install # pnpm install
npm install react # pnpm add react
npm install -D vitest # pnpm add -D vitest
npm uninstall react # pnpm remove react
npm run dev # pnpm dev (or pnpm run dev)
npm test # pnpm test
npx create-next-app # pnpm create next-app (or pnpm dlx create-next-app)When to reach for this: When starting a new project or evaluating whether to switch from npm to pnpm. Understanding the tradeoffs helps you make an informed choice rather than switching based on hype.
Deep Dive
How npm Installs Packages
npm uses a flat node_modules structure. When you run npm install, it:
- Reads
package.jsonfor direct dependencies. - Resolves the full dependency tree (dependencies of dependencies).
- Hoists as many packages as possible to the top level of
node_modules/to reduce duplication. - Writes
package-lock.jsonto lock exact versions.
node_modules/
react/
react-dom/
scheduler/ # dependency of react-dom, hoisted to top level
loose-envify/ # transitive dependency, also hoisted
The problem with hoisting: your code can import packages that are not in your package.json because they happen to be hoisted. This is called phantom dependencies — your code works by accident until someone removes the package that pulled in the transitive dependency.
How pnpm Installs Packages
pnpm uses a content-addressable store and symlinks. When you run pnpm install, it:
- Downloads packages to a global store (
~/.local/share/pnpm/store/on macOS/Linux). - Creates a virtual store inside
node_modules/.pnpm/with hard links to the global store. - Creates symlinks in
node_modules/that point only to your direct dependencies.
node_modules/
react -> .pnpm/react@19.1.0/node_modules/react
react-dom -> .pnpm/react-dom@19.1.0/node_modules/react-dom
.pnpm/
react@19.1.0/
node_modules/
react/ # hard link to global store
react-dom@19.1.0/
node_modules/
react-dom/ # hard link to global store
scheduler/ # symlink to .pnpm/scheduler@.../
This layout means:
- You cannot import packages you did not declare in
package.json— no phantom dependencies. - Each version of a package is stored once on disk across all your projects.
node_modulesis smaller and installs are faster because files are linked, not copied.
Key Differences
| Feature | npm | pnpm |
|---|---|---|
| node_modules structure | Flat, hoisted | Symlinked, strict |
| Disk usage | Full copy per project | Content-addressable store, hard links |
| Phantom dependencies | Allowed (hoisting) | Blocked by default |
| Install speed | Moderate | Faster (links instead of copies) |
| Lockfile | package-lock.json | pnpm-lock.yaml |
| Monorepo support | Workspaces (npm 7+) | Workspaces + pnpm-workspace.yaml |
| Ships with Node.js | Yes | No (install separately or via Corepack) |
| npx equivalent | npx | pnpm dlx |
| Run scripts shorthand | npm run dev | pnpm dev |
| Peer dependency handling | Auto-installed (npm 7+) | Strict — warns or errors on missing peers |
| Corepack support | n/a (bundled) | Yes — "packageManager": "pnpm@9.x" in package.json |
Disk Space Savings
With npm, if you have 10 projects that all use React 19, React is copied 10 times. With pnpm, React 19 is stored once in the global store and hard-linked into each project. On a machine with many projects, pnpm can save gigabytes of disk space.
# See how much space pnpm's store is using
pnpm store status
# Clean up unreferenced packages from the store
pnpm store pruneInstall Speed
pnpm is consistently faster than npm for several reasons:
- Hard links instead of file copies.
- Parallel downloads with concurrent network requests.
- Content-addressable store means previously downloaded packages are available instantly.
Typical benchmarks show pnpm 2-3x faster than npm on cold installs and significantly faster on warm installs (when packages are already in the store).
Strictness and Safety
pnpm's strict node_modules layout catches real bugs:
// This works with npm (phantom dependency) but fails with pnpm:
import chalk from 'chalk';
// Error: chalk is not listed in package.jsonIf your code only works because of npm's hoisting, pnpm will surface the missing dependency immediately. This is a feature, not a bug — it means your package.json is an accurate description of what your project actually uses.
Monorepo Support
Both npm and pnpm support workspaces, but pnpm's implementation is more mature:
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'# Run a command in a specific workspace
pnpm --filter @myapp/web dev
# Install a dependency in a specific workspace
pnpm --filter @myapp/web add zod
# Run a command across all workspaces
pnpm -r buildpnpm's --filter flag is more powerful than npm's --workspace flag, supporting glob patterns, dependency-based filtering, and changed-since-commit filtering.
Command Comparison
| Task | npm | pnpm |
|---|---|---|
| Install all deps | npm install | pnpm install |
| Add a package | npm install zod | pnpm add zod |
| Add a dev dep | npm install -D vitest | pnpm add -D vitest |
| Remove a package | npm uninstall zod | pnpm remove zod |
| Run a script | npm run dev | pnpm dev |
| Run tests | npm test | pnpm test |
| Execute a binary | npx create-next-app | pnpm dlx create-next-app |
| Update deps | npm update | pnpm update |
| Audit for vulns | npm audit | pnpm audit |
| List installed | npm ls | pnpm ls |
| Global install | npm install -g tsx | pnpm add -g tsx |
Migrating from npm to pnpm
# 1. Install pnpm
corepack enable
corepack prepare pnpm@latest --activate
# 2. Delete npm artifacts
rm -rf node_modules package-lock.json
# 3. Install with pnpm
pnpm install
# 4. (Optional) Pin pnpm version in package.json
# Add: "packageManager": "pnpm@9.15.4"
# 5. Update your scripts — npm run -> pnpm
# Update CI config (GitHub Actions, etc.)The migration is usually painless. If it fails, it is almost always because of phantom dependencies — packages your code imports but that are missing from package.json. Fix them by adding the missing packages explicitly.
When to Use npm
- You want zero setup. npm ships with Node.js. Nothing extra to install.
- You are following a tutorial. Most tutorials and documentation use npm commands. Translating to pnpm is trivial but adds friction when learning.
- Your team is on npm and there is no pain. Switching package managers mid-project has a cost. If npm is working fine, there is no urgent reason to switch.
- CI caching is already optimized for npm. Some CI providers have better built-in caching for
node_modulesandpackage-lock.json.
When to Use pnpm
- You work on many projects. The disk space savings are substantial.
- You want strict dependency correctness. No phantom dependencies means fewer surprises in production.
- You run a monorepo. pnpm's workspace and filtering support is best-in-class.
- Install speed matters. Faster installs mean faster CI and faster developer onboarding.
- You want to enforce a specific package manager. Corepack +
"packageManager"inpackage.jsonensures everyone uses the same tool and version.
Gotchas
- Phantom dependencies break on migration. If your code imports a package not in
package.json, it works with npm but fails with pnpm. The fix is to add the missing package topackage.json— this is the correct thing to do anyway. - Some packages assume flat
node_modules. Older packages that userequire.resolvetricks or walk up the directory tree may fail with pnpm's symlinked layout. The workaround isnode-linker=hoistedin.npmrc, but this defeats pnpm's strictness. - Lockfile format is different. npm uses
package-lock.json(JSON). pnpm usespnpm-lock.yaml(YAML). You cannot use both — pick one and add the other to.gitignore. npxdoes not exist in pnpm. Usepnpm dlxinstead. Example:pnpm dlx create-next-appinstead ofnpx create-next-app.- Peer dependency strictness. pnpm is stricter about peer dependencies than npm. If a package expects
reactas a peer dependency and you have not installed it, pnpm warns or errors. Fix by installing the peer dependency. - CI setup differs. GitHub Actions and other CI providers need
pnpm/action-setupor Corepack to install pnpm before runningpnpm install. - Global store location. pnpm's global store can grow large over time. Run
pnpm store pruneperiodically to clean up packages no longer referenced by any project.
Alternatives
| Tool | Strengths | Weaknesses |
|---|---|---|
| npm | Ships with Node.js, widest ecosystem support | Flat hoisting, phantom deps, slower installs |
| pnpm | Fast, strict, disk efficient, great monorepo support | Extra install step, stricter may break legacy packages |
| Yarn Classic (1.x) | Stable, widely used | Maintenance mode, similar hoisting issues as npm |
| Yarn Berry (3+) | Plug'n'Play, zero-installs | Steep learning curve, compatibility issues, PnP can confuse tools |
| Bun | Extremely fast installs, runtime + bundler | Newer, not 100% Node.js compatible, smaller ecosystem |
FAQs
Can I use pnpm and npm in the same project?
You should not. Each has its own lockfile format (package-lock.json vs pnpm-lock.yaml) and node_modules structure. Pick one and commit only its lockfile. Add the other's lockfile to .gitignore.
Does pnpm work with Next.js?
Yes. Next.js works with pnpm out of the box. pnpm create next-app scaffolds a new project. The only caveat is that some Next.js examples use npm commands in their READMEs — just substitute with pnpm equivalents.
What is Corepack?
Corepack is a tool bundled with Node.js (18.17+) that manages package manager versions. When you set "packageManager": "pnpm@9.15.4" in package.json, Corepack ensures everyone uses exactly that version. Enable it with corepack enable.
Is pnpm faster than npm?
Yes, in most benchmarks. pnpm is 2-3x faster on cold installs and even faster on warm installs (when packages are already in the global store). The speed comes from hard linking files instead of copying them and from parallel network requests.
What are phantom dependencies?
Packages that your code imports but that are not listed in your package.json. They work with npm because hoisting makes transitive dependencies accessible at the top level. pnpm blocks this by default, which catches real bugs — your code should only import packages you explicitly depend on.
How do I run a one-off command with pnpm (like npx)?
Use pnpm dlx. For example: pnpm dlx create-next-app instead of npx create-next-app. The dlx command downloads and runs the package without installing it permanently.
Can I switch back to npm from pnpm?
Yes. Delete node_modules and pnpm-lock.yaml, then run npm install. This generates a fresh package-lock.json. If you had phantom dependencies that pnpm caught, they will silently "work" again under npm.
Does pnpm support private registries?
Yes. Configure the registry in .npmrc the same way you would for npm: registry=https://your-registry.example.com/. pnpm reads .npmrc files.
What about Yarn? How does it compare?
Yarn Classic (1.x) is similar to npm with flat hoisting. Yarn Berry (3+) uses Plug'n'Play which avoids node_modules entirely but has compatibility issues with some packages. pnpm sits in the middle — strict and fast without the compatibility headaches of PnP.
Should I switch my existing project to pnpm?
If npm is working fine and your team is comfortable, there is no urgency. Consider switching if you are starting a new project, running a monorepo, or experiencing slow installs and disk space pressure. The migration is usually straightforward.