Skip to main content

pnpm: Why I Switched from npm (And You Should Too)

January 7, 2026

by Lucas DeAraujo

pnpm.ts
# Install pnpm
npm install -g pnpm

# Migrate existing project
pnpm import && pnpm install

You run npm install and wait. And wait. The progress bar crawls. You check your node_modules folder. It is 800MB for a project with 12 dependencies.

You have three projects on your machine that all use React 18.2.0. npm dutifully stores three separate copies. 150MB times three. You are paying for the same bytes three times over.

This is the npm tax. Every JavaScript developer pays it. Most do not realize there is an alternative.

pnpm fixes this. It is faster, uses less disk space, and catches dependency bugs that npm silently ignores. The migration takes five minutes.

#How pnpm Works

pnpm stores every package version exactly once on your machine, in a global content-addressable store. When you install a package, pnpm creates a hard link from that store to your project's node_modules.

# Global store (one copy per version)
~/.pnpm-store/
  v3/
    files/
      [email protected]/
      [email protected]/
      [email protected]/

# Project A (hard links, not copies)
~/projects/project-a/node_modules/
  react -> ~/.pnpm-store/v3/files/[email protected]/
  lodash -> ~/.pnpm-store/v3/files/[email protected]/

# Project B (same links)
~/projects/project-b/node_modules/
  react -> ~/.pnpm-store/v3/files/[email protected]/

Ten projects using React 18.2.0? One copy on disk. Hard links are instant and take no additional space. Your node_modules folder becomes a directory of pointers.

The store is content-addressable. This means packages are identified by their content hash, not just their name and version. If two different packages contain the same file, pnpm stores it once.

#Speed Comparison

I tested both package managers on a real project with 847 dependencies. Clean install, no cache.

ScenarionpmpnpmDifference
Clean install (cold cache)42s18s2.3x faster
Clean install (warm cache)38s8s4.7x faster
Adding one package12s3s4x faster
node_modules size412MB178MB2.3x smaller

The warm cache is where pnpm really shines. Once a package is in your global store, installing it in a new project is nearly instant. You are not downloading anything. You are creating links.

#The Phantom Dependency Problem

npm has a silent bug factory called flat node_modules. When you install a package, npm also installs all its dependencies and puts them at the top level. Your code can import these transitive dependencies even though you did not explicitly install them.

// package.json only lists "express"
// But express depends on "cookie", so npm installs it
 
// This works! But it shouldn't.
import cookie from 'cookie';

This is a phantom dependency. Your code depends on something that is not in your package.json. It works until express decides to use a different cookie library, or pins a version incompatible with your usage. Then your build breaks and you have no idea why.

pnpm fixes this with a non-flat node_modules structure. Your node_modules only contains packages you explicitly installed. Transitive dependencies are nested.

# pnpm node_modules structure
node_modules/
  .pnpm/                    # All packages live here
    [email protected]/
      node_modules/
        express/
        cookie/             # express's dependency
    [email protected]/
      node_modules/
        cookie/
  express -> .pnpm/[email protected]/node_modules/express
  # No direct link to cookie!

Try to import cookie without adding it to your package.json? pnpm fails immediately.

Error: Cannot find module 'cookie'

This catches bugs before they reach production. You will thank pnpm when you discover a phantom dependency that has been silently working for months.

#Strict Mode

pnpm's strictness can be configured. The default is already stricter than npm, but you can go further.

# .npmrc
strict-peer-dependencies=true
auto-install-peers=false

With strict peer dependencies, pnpm fails if peer dependency requirements are not met. npm just warns. This surfaces version conflicts immediately instead of letting them cause mysterious runtime bugs.

#Workspace Support for Monorepos

pnpm has first-class monorepo support with workspaces. The configuration is simpler than npm workspaces and more powerful.

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
  - 'tools/*'

That is the entire configuration. pnpm automatically discovers all packages in those directories.

#Installing Dependencies

# Install everything in all workspaces
pnpm install
 
# Add a dependency to a specific workspace
pnpm add lodash --filter @myapp/web
 
# Add a workspace package as a dependency
pnpm add @myapp/shared --filter @myapp/web --workspace

The --filter flag is powerful. It accepts package names, directory paths, and glob patterns.

# Run tests in all packages that changed since main
pnpm test --filter '...[main]'
 
# Build all packages that depend on @myapp/core
pnpm build --filter '...@myapp/core'
 
# Run in all packages under apps/
pnpm dev --filter './apps/*'

#Workspace Protocol

pnpm uses a workspace protocol for local packages.

// apps/web/package.json
{
  "dependencies": {
    "@myapp/shared": "workspace:*",
    "@myapp/ui": "workspace:^1.0.0"
  }
}

workspace:* always uses the local version. workspace:^1.0.0 uses the local version during development but publishes as ^1.0.0.

When you publish, pnpm replaces the workspace protocol with actual version numbers. Your published package has normal dependency declarations.

#Migration Guide

Switching from npm takes five minutes.

#Step 1: Install pnpm

# Using npm (ironic but works)
npm install -g pnpm
 
# Or using corepack (built into Node 16.13+)
corepack enable
corepack prepare pnpm@latest --activate
 
# Or standalone script
curl -fsSL https://get.pnpm.io/install.sh | sh -

#Step 2: Import Existing Lock File

# Navigate to your project
cd my-project
 
# Delete node_modules
rm -rf node_modules
 
# Import package-lock.json to pnpm-lock.yaml
pnpm import
 
# Install with pnpm
pnpm install

pnpm reads your package-lock.json and creates an equivalent pnpm-lock.yaml. Your dependencies stay exactly the same.

#Step 3: Update CI/CD

Replace npm commands with pnpm equivalents.

# Before (GitHub Actions)
- run: npm ci
- run: npm test
- run: npm run build
 
# After
- uses: pnpm/action-setup@v2
  with:
    version: 8
- run: pnpm install --frozen-lockfile
- run: pnpm test
- run: pnpm run build

#Step 4: Add .npmrc (Optional)

Create an .npmrc file to configure pnpm behavior.

# .npmrc
shamefully-hoist=false
strict-peer-dependencies=true
auto-install-peers=true

shamefully-hoist=true flattens node_modules like npm. Use this only if you have compatibility issues. The name is intentional. pnpm is shaming you for needing it.

#Step 5: Update .gitignore

node_modules
pnpm-lock.yaml  # Or commit it, depending on your preference

Most teams commit the lock file. It ensures reproducible builds.

#Command Comparison

pnpm commands mirror npm closely. The transition is intuitive.

npmpnpmNotes
npm installpnpm installSame
npm install lodashpnpm add lodashadd instead of install
npm uninstall lodashpnpm remove lodashremove instead of uninstall
npm run buildpnpm buildNo run needed for scripts
npm cipnpm install --frozen-lockfileFails if lock file needs update
npm updatepnpm updateSame
npx create-react-apppnpm dlx create-react-appdlx instead of npx

One convenience: pnpm build is shorthand for pnpm run build. You can drop the run for any script in your package.json.

#When to Stick with npm

pnpm is not always the right choice.

Stay with npm if:

  • Your team is not ready to learn a new tool
  • You have CI/CD that is hard to modify
  • You depend on packages with known pnpm incompatibilities
  • You need native npm features like provenance attestations

Consider npm for:

  • Simple projects with few dependencies
  • Teaching beginners who will encounter npm elsewhere
  • Organizations with strict tooling policies

npm is fine. It works. But if you install packages frequently, work on multiple projects, or maintain monorepos, pnpm pays for itself immediately.

#When to Use Yarn

Yarn is the other alternative. Here is when it makes sense.

Use Yarn if:

  • You need Plug'n'Play (PnP) for even stricter dependency resolution
  • Your team already uses it
  • You want zero-installs in your repo

Use pnpm over Yarn if:

  • You want simpler configuration
  • You prefer a flatter learning curve
  • You do not need PnP

Yarn Berry (v2+) is powerful but complex. pnpm offers most of the benefits with less friction.

#Summary

pnpm is npm but faster and smarter. It stores packages once globally and links them into your projects. This saves disk space and installation time.

Key benefits:

  • 2-4x faster installs
  • 50% less disk usage
  • Catches phantom dependencies
  • Native monorepo support

Key commands:

  • pnpm add instead of npm install <package>
  • pnpm install --frozen-lockfile instead of npm ci
  • pnpm dlx instead of npx

Migration:

rm -rf node_modules
pnpm import
pnpm install

Stop paying the npm tax. Switch to pnpm.

Ready to get started?

Let's build something great together

Whether you need managed IT, security, cloud, or custom development, we're here to help. Reach out and let's talk about your technology needs.