React SME Cookbook
All FAQs
pnpmnpmpackage-managerinstallnode-modules

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:

  1. Reads package.json for direct dependencies.
  2. Resolves the full dependency tree (dependencies of dependencies).
  3. Hoists as many packages as possible to the top level of node_modules/ to reduce duplication.
  4. Writes package-lock.json to 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:

  1. Downloads packages to a global store (~/.local/share/pnpm/store/ on macOS/Linux).
  2. Creates a virtual store inside node_modules/.pnpm/ with hard links to the global store.
  3. 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_modules is smaller and installs are faster because files are linked, not copied.

Key Differences

Featurenpmpnpm
node_modules structureFlat, hoistedSymlinked, strict
Disk usageFull copy per projectContent-addressable store, hard links
Phantom dependenciesAllowed (hoisting)Blocked by default
Install speedModerateFaster (links instead of copies)
Lockfilepackage-lock.jsonpnpm-lock.yaml
Monorepo supportWorkspaces (npm 7+)Workspaces + pnpm-workspace.yaml
Ships with Node.jsYesNo (install separately or via Corepack)
npx equivalentnpxpnpm dlx
Run scripts shorthandnpm run devpnpm dev
Peer dependency handlingAuto-installed (npm 7+)Strict — warns or errors on missing peers
Corepack supportn/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 prune

Install 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.json

If 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 build

pnpm's --filter flag is more powerful than npm's --workspace flag, supporting glob patterns, dependency-based filtering, and changed-since-commit filtering.

Command Comparison

Tasknpmpnpm
Install all depsnpm installpnpm install
Add a packagenpm install zodpnpm add zod
Add a dev depnpm install -D vitestpnpm add -D vitest
Remove a packagenpm uninstall zodpnpm remove zod
Run a scriptnpm run devpnpm dev
Run testsnpm testpnpm test
Execute a binarynpx create-next-apppnpm dlx create-next-app
Update depsnpm updatepnpm update
Audit for vulnsnpm auditpnpm audit
List installednpm lspnpm ls
Global installnpm install -g tsxpnpm 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_modules and package-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" in package.json ensures everyone uses the same tool and version.

Gotchas

  1. 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 to package.json — this is the correct thing to do anyway.
  2. Some packages assume flat node_modules. Older packages that use require.resolve tricks or walk up the directory tree may fail with pnpm's symlinked layout. The workaround is node-linker=hoisted in .npmrc, but this defeats pnpm's strictness.
  3. Lockfile format is different. npm uses package-lock.json (JSON). pnpm uses pnpm-lock.yaml (YAML). You cannot use both — pick one and add the other to .gitignore.
  4. npx does not exist in pnpm. Use pnpm dlx instead. Example: pnpm dlx create-next-app instead of npx create-next-app.
  5. Peer dependency strictness. pnpm is stricter about peer dependencies than npm. If a package expects react as a peer dependency and you have not installed it, pnpm warns or errors. Fix by installing the peer dependency.
  6. CI setup differs. GitHub Actions and other CI providers need pnpm/action-setup or Corepack to install pnpm before running pnpm install.
  7. Global store location. pnpm's global store can grow large over time. Run pnpm store prune periodically to clean up packages no longer referenced by any project.

Alternatives

ToolStrengthsWeaknesses
npmShips with Node.js, widest ecosystem supportFlat hoisting, phantom deps, slower installs
pnpmFast, strict, disk efficient, great monorepo supportExtra install step, stricter may break legacy packages
Yarn Classic (1.x)Stable, widely usedMaintenance mode, similar hoisting issues as npm
Yarn Berry (3+)Plug'n'Play, zero-installsSteep learning curve, compatibility issues, PnP can confuse tools
BunExtremely fast installs, runtime + bundlerNewer, 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.