
If you maintain several frontend projects at once, node_modules quickly becomes a black hole for disk space. A single React, Next.js, Vite, or mini-program project can easily start at several hundred megabytes — and when multiple projects install the same dependencies separately, the waste adds up fast.
That's where pnpm shines. Instead of storing a copy of each package in every project, it places packages in a global content-addressable store and links them into each project. The result: faster installs, lower disk usage, and stricter dependency resolution.
Why pnpm
Traditional npm or older yarn work more like "one copy per project":
project-a/node_modules/react <- one copy
project-b/node_modules/react <- another copy
project-c/node_modules/react <- yet another copy
pnpm centralizes packages in a global store:
~/.pnpm-store/react@18.x <- one copy globally
project-a/node_modules <- linked to store
project-b/node_modules <- linked to store
project-c/node_modules <- linked to store
For day-to-day development, this delivers three concrete benefits:
- Saves disk space: multiple projects share the same dependency content — especially useful on a MacBook with many local projects.
- Faster installs: packages already downloaded are reused directly from the local store.
- More controlled dependencies: pnpm does not allow projects to access undeclared dependencies by default, surfacing phantom dependency issues earlier.
Installing pnpm on Mac
If you already use Homebrew, installing through it keeps your toolchain consistent:
brew install pnpm
Verify the installation:
pnpm --version
If you want to store pnpm's global cache on a specific drive — such as an external SSD — configure store-dir:
pnpm config set store-dir /Volumes/your-disk/pnpm-store
Most users don't need to change this. The default is perfectly fine. Only consider a custom path if your internal drive is tight on space or you want to centralize your dev caches.
npm vs pnpm Command Reference
| Task | npm | pnpm |
|---|---|---|
| Install all dependencies | npm install |
pnpm install |
| Add a production dependency | npm install react |
pnpm add react |
| Add a dev dependency | npm install -D eslint |
pnpm add -D eslint |
| Install a global tool | npm install -g xxx |
pnpm add -g xxx |
| Remove a dependency | npm uninstall xxx |
pnpm remove xxx |
| Run a script | npm run dev |
pnpm dev |
| Execute a one-off tool | npx create-next-app |
pnpm dlx create-next-app |
| View dependency tree | npm ls |
pnpm list |
| Check for outdated packages | npm outdated |
pnpm outdated |
| Upgrade dependencies | npm update |
pnpm update |
| Clean store | npm cache clean |
pnpm store prune |
Most scripts can omit run:
pnpm dev
pnpm build
pnpm lint
Use the full form only when a script name conflicts with a pnpm built-in:
pnpm run test
Migrating an Existing Project to pnpm
Switching from npm or yarn is a three-step process.
Step 1 — remove the old dependency directory and lockfile:
rm -rf node_modules
rm -f package-lock.json yarn.lock
Step 2 — reinstall dependencies:
pnpm install
This generates a fresh pnpm-lock.yaml.
Step 3 — verify the project starts and builds correctly:
pnpm dev
pnpm build
For larger projects, avoid migrating directly on the main branch. A safer approach is to branch first:
git checkout -b feat/migrate-to-pnpm
rm -rf node_modules package-lock.json yarn.lock
pnpm install
pnpm dev
pnpm build
Once dev, build, test, and deployment all pass, merge the migration.
Which Projects to Migrate First
Not every project needs to switch immediately. Prioritize by risk and reward:
| Project type | Timing | Reason |
|---|---|---|
| New projects | Start with pnpm | No legacy lockfiles or CI compatibility concerns |
| Active projects with many dependencies | Migrate early | Disk and install-time savings are most noticeable |
| Internal tools and admin panels | Good candidate | Usually controllable, low verification cost |
| Projects nearing a deadline | Hold off | Stability first — don't add variables close to release |
| SSR or complex 3D dependency stacks | Test on a branch | Validate builds, dynamic imports, and deploy environment carefully |
Switching package managers isn't a product feature. If you're in a sprint, wait for the next iteration.
Common Issues and Fixes
Package not found
pnpm's node_modules is not a traditional flat structure. Well-maintained packages declare all their dependencies, but some older or poorly configured tools quietly rely on packages hoisted to the project root — the classic "phantom dependency" problem.
If you hit a compatibility issue, add .npmrc to your project root:
shamefully-hoist=true
This makes pnpm's hoisting behavior closer to npm, improving compatibility at the cost of some dependency isolation. Treat it as a fallback, not a default for all projects.
Mini-program toolchains can't find dependencies
WeChat mini-programs and similar platforms often have their own dependency build flows. After installing with pnpm, if dependencies aren't available in the developer tool, try reinstalling first:
pnpm install
Then run "Build npm" again inside the developer tool. If the toolchain has poor symlink support, consider shamefully-hoist=true or stick with npm for that project.
AI coding assistants defaulting to npm
If you use Claude Code, Codex, or other AI coding tools, document your package manager choice explicitly to prevent them from running npm install and generating a package-lock.json.
Add something like this to your README.md, CONTEXT.md, or project conventions doc:
## Development Conventions
- Package manager: pnpm
- Install dependencies: pnpm install
- Add a package: pnpm add <package>
- Do not run npm install or commit package-lock.json
Resolving pnpm-lock.yaml conflicts
In collaborative projects, pnpm-lock.yaml can also produce merge conflicts. Lockfiles aren't meant to be hand-edited in bulk. The safest approach is to pick one side's version and reinstall.
For example, keep your current branch's lockfile and re-resolve:
git checkout HEAD -- pnpm-lock.yaml
pnpm install
Or, if you're merging from the target branch, take that side and run pnpm install again. The goal is to let pnpm regenerate a consistent resolution — not to manually patch YAML.
Migration Checklist for Next.js Projects
Stacks combining Next.js, Three.js, GSAP, or Howler.js tend to be more sensitive around SSR, browser APIs, and build environments. Verify these specifically before merging:
git checkout -b feat/migrate-to-pnpm
rm -rf node_modules package-lock.json yarn.lock
pnpm install
pnpm list next react react-dom
pnpm dev
pnpm build
If your project includes browser-only libraries, keep the existing SSR-safe patterns — next/dynamic with ssr: false. Switching package managers doesn't fix SSR issues; it only changes how dependencies are installed and resolved.
Useful Advanced Commands
Find out why a package is installed:
pnpm why react
Install production dependencies only (common in deploy environments):
pnpm install --prod
Interactively upgrade dependencies:
pnpm update --interactive
Check store integrity:
pnpm store status
Remove store content no longer used by any project:
pnpm store prune
When to Use pnpm Workspaces
Once you start putting multiple frontend apps, component libraries, design tokens, and scripts into a single repository, pnpm workspace becomes valuable.
A typical structure looks like this:
project/
├── pnpm-workspace.yaml
├── apps/
│ ├── web/
│ └── admin/
└── packages/
├── ui/
└── config/
Create pnpm-workspace.yaml at the root:
packages:
- 'apps/*'
- 'packages/*'
Workspaces are built for monorepos. Don't introduce them just because they sound sophisticated. If you have a single app today, plain pnpm is enough. Upgrade to a workspace when you genuinely need to share components, config, or tooling across multiple packages.
A Phased Approach
If you haven't used pnpm in a production project yet, here's a reasonable pace:
Now -> Pick a lower-risk active project and migrate it
This week -> Get comfortable with pnpm add, pnpm dev, pnpm why
Next sprint -> Branch-test migration on a more critical project
Later -> Consider pnpm workspace when you need cross-package sharing
One-line summary: pnpm is a great fit for multi-project developers, but don't stop at "install succeeded." The real validation is getting dev, build, test, and deployment all the way through.
Subscribe to FreeMac
Weekly picks: free Mac software reviews, trusted source updates, alternatives, and low-friction guides.