name: pnpm-package-manager description: pnpm package management, workspace setup, dependency updates, and CI integration
pnpm Package Manager
Overview
pnpm best practices for frontend projects:
- pnpm commands (add, remove, update, run)
.npmrcconfiguration- Lock file management
- CI/CD integration
- Workspace support (monorepos)
- Troubleshooting common issues
Hard Rules
- ALWAYS use
pnpmfor all package operations — NEVERnpmoryarnin pnpm projects - ALWAYS use
pnpm runto execute scripts — NEVERnpm runoryarn - ALWAYS commit
pnpm-lock.yaml— the lock file MUST be in version control - ALWAYS use
pnpm dlxinstead ofnpxfor one-off package execution - ALWAYS use
--frozen-lockfilein CI — NEVER allow lock file modifications in CI - NEVER delete
pnpm-lock.yamlto "fix" issues — resolve the underlying problem - NEVER use
shamefully-hoist=trueunless absolutely required by a broken dependency - ALWAYS check for existing lock file before running
pnpm installin a new project
Detecting pnpm Projects
Before using pnpm commands, verify the project uses pnpm:
# Check for pnpm lock file
ls pnpm-lock.yaml
# Check for pnpm workspace
ls pnpm-workspace.yaml
# Verify pnpm is available
pnpm --version
If pnpm-lock.yaml exists → use pnpm. If package-lock.json exists → use npm. If yarn.lock exists → use yarn. Never mix package managers.
Essential Commands
Installing Dependencies
# Install all dependencies from lock file
pnpm install
# Install with frozen lock file (CI)
pnpm install --frozen-lockfile
# Install only production dependencies
pnpm install --prod
Adding Dependencies
# Add a runtime dependency
pnpm add react
# Add a dev dependency
pnpm add -D vitest @testing-library/react
# Add a specific version
pnpm add react@18.3.1
# Add to a specific workspace package (monorepo)
pnpm add lodash --filter @myapp/utils
Removing Dependencies
# Remove a dependency
pnpm remove lodash
# Remove from a specific workspace package
pnpm remove lodash --filter @myapp/utils
Updating Dependencies
# Update all dependencies to latest within semver range
pnpm update
# Update a specific package
pnpm update react
# Update to latest version (ignore semver range)
pnpm update react --latest
# Interactive update — shows outdated packages
pnpm update --interactive
# Check for outdated packages without updating
pnpm outdated
Running Scripts
# Run a script from package.json
pnpm run dev
pnpm run build
pnpm run test
pnpm run lint
# Shorthand for common scripts
pnpm dev # Same as pnpm run dev
pnpm test # Same as pnpm run test
pnpm start # Same as pnpm run start
# Run script in a specific workspace package
pnpm run build --filter @myapp/web
# Run script in all workspace packages
pnpm run build --recursive
# or shorthand
pnpm -r build
One-Off Execution (dlx)
# ✅ GOOD: Use pnpm dlx instead of npx
pnpm dlx create-vite my-app --template react-ts
pnpm dlx shadcn@latest add button
pnpm dlx tsc --noEmit
# ❌ BAD: Using npx in a pnpm project
npx create-vite my-app # WRONG — use pnpm dlx
Package.json Scripts Template
Standard Frontend Scripts
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"test": "vitest run --coverage",
"test:watch": "vitest",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"typecheck": "tsc --noEmit",
"lint": "eslint . --fix",
"lint:check": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}
Alternative with Biome
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"test": "vitest run --coverage",
"test:watch": "vitest",
"typecheck": "tsc --noEmit",
"lint": "biome check --fix .",
"lint:check": "biome check .",
"format": "biome format --write .",
"format:check": "biome format ."
}
}
.npmrc Configuration
Recommended Settings
# .npmrc
strict-peer-dependencies=true # Fail on peer dependency conflicts
auto-install-peers=true # Auto-install peer deps
shamefully-hoist=false # Strict node_modules isolation (default)
What Each Setting Does
| Setting | Value | Purpose |
|---|---|---|
strict-peer-dependencies |
true |
Fail install if peer deps conflict — prevents runtime issues |
auto-install-peers |
true |
Automatically install peer dependencies — reduces manual work |
shamefully-hoist |
false |
Keep strict node_modules structure — prevents phantom dependencies |
When to Use shamefully-hoist=true
Only when a dependency has a bug that requires hoisting. Document WHY:
# .npmrc
# Required: [package-name] v1.2.3 doesn't resolve correctly without hoisting
# See: https://github.com/some/issue/123
shamefully-hoist=true
CI/CD Integration
GitHub Actions with pnpm
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Type check
run: pnpm typecheck
- name: Lint
run: pnpm lint:check
- name: Test
run: pnpm test
- name: Build
run: pnpm build
Key CI Principles
--frozen-lockfile— Lock file must not change in CI. If it would, the build fails.- Cache pnpm store —
actions/setup-nodewithcache: 'pnpm'handles this. - Pipeline order: typecheck → lint → test → build (fail fast on cheapest checks first).
Workspace Support (Monorepos)
Workspace Configuration
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
Directory Structure
my-monorepo/
pnpm-workspace.yaml
package.json # Root — shared scripts and dev deps
apps/
web/ # @myapp/web
package.json
admin/ # @myapp/admin
package.json
packages/
ui/ # @myapp/ui
package.json
utils/ # @myapp/utils
package.json
Workspace Commands
# Run build in all packages
pnpm -r build
# Run build only in @myapp/web
pnpm --filter @myapp/web build
# Run build in @myapp/web and all its dependencies
pnpm --filter @myapp/web... build
# Add a workspace package as dependency
pnpm add @myapp/ui --filter @myapp/web --workspace
# Add a dev dependency to the root
pnpm add -D typescript -w
Cross-Package Dependencies
// apps/web/package.json
{
"dependencies": {
"@myapp/ui": "workspace:*",
"@myapp/utils": "workspace:*"
}
}
workspace:* — Always resolve to the local workspace version. Published versions use the actual version number.
Troubleshooting
Phantom Dependencies
Problem: Code imports a package that isn't in package.json but works because another package hoisted it.
// ❌ This works in npm/yarn but fails in pnpm (strict isolation)
import something from 'phantom-dep'; // Not in your package.json
Fix: Add the missing dependency explicitly:
pnpm add phantom-dep
Peer Dependency Conflicts
Problem: pnpm install fails with peer dependency errors.
# Check what's conflicting
pnpm install 2>&1 | grep "WARN"
# Option 1: Update the conflicting package
pnpm update conflicting-package
# Option 2: If the peer dep is acceptable, override in .npmrc
# (only if you've verified compatibility)
In package.json — use pnpm.overrides as last resort:
{
"pnpm": {
"overrides": {
"react": "^18.3.0"
}
}
}
Stale Lock File
Problem: Lock file is out of sync with package.json.
# Regenerate lock file
pnpm install
# Verify the lock file is correct
git diff pnpm-lock.yaml
# Commit the updated lock file
git add pnpm-lock.yaml
Never delete pnpm-lock.yaml — it contains resolved versions that ensure reproducible builds.
Cache Issues
# Clear pnpm store cache
pnpm store prune
# Verify store integrity
pnpm store status
# Nuclear option — clear everything and reinstall
rm -rf node_modules
pnpm install
Module Resolution Issues
# Check which version of a package is installed
pnpm list react
# Check why a package is installed (dependency tree)
pnpm why react
# List all installed packages
pnpm list --depth=0
Common Mistakes
❌ Using npm/yarn in a pnpm Project
# WRONG: Mixing package managers
npm install lodash # Creates package-lock.json — conflicts!
yarn add lodash # Creates yarn.lock — conflicts!
# CORRECT: Always use pnpm
pnpm add lodash
❌ Deleting Lock File
# WRONG: "Fix" by deleting lock file
rm pnpm-lock.yaml
pnpm install # Generates new lock with potentially different versions
# CORRECT: Fix the actual issue
pnpm install # Usually resolves conflicts
pnpm update affected-package # Update specific package
❌ Missing --frozen-lockfile in CI
# WRONG: Allows lock file changes in CI
- run: pnpm install
# CORRECT: Fails if lock file would change
- run: pnpm install --frozen-lockfile
❌ Using npx Instead of pnpm dlx
# WRONG: npx in a pnpm project
npx create-vite my-app
# CORRECT: pnpm dlx
pnpm dlx create-vite my-app
Summary
- ✅
pnpmfor all package operations — never npm/yarn in pnpm projects - ✅
pnpm runfor scripts,pnpm dlxinstead of npx - ✅
pnpm-lock.yamlalways committed - ✅
--frozen-lockfilein CI - ✅
.npmrcwith strict peer deps and no shameful hoisting - ✅
pnpm/action-setup@v4for GitHub Actions - ✅
workspace:*for monorepo cross-dependencies - ✅ Fix phantom deps by adding explicit dependencies