| 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)
.npmrc configuration
- Lock file management
- CI/CD integration
- Workspace support (monorepos)
- Troubleshooting common issues
Hard Rules
These rules are NON-NEGOTIABLE. Violating any of them is a bug.
- ALWAYS use
pnpm for all package operations — NEVER npm or yarn in pnpm projects
- ALWAYS use
pnpm run to execute scripts — NEVER npm run or yarn
- ALWAYS commit
pnpm-lock.yaml — the lock file MUST be in version control
- ALWAYS use
pnpm dlx instead of npx for one-off package execution
- ALWAYS use
--frozen-lockfile in CI — NEVER allow lock file modifications in CI
- NEVER delete
pnpm-lock.yaml to "fix" issues — resolve the underlying problem
- NEVER use
shamefully-hoist=true unless absolutely required by a broken dependency
- ALWAYS check for existing lock file before running
pnpm install in a new project
Detecting pnpm Projects
Before using pnpm commands, verify the project uses pnpm:
ls pnpm-lock.yaml
ls pnpm-workspace.yaml
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
pnpm install
pnpm install --frozen-lockfile
pnpm install --prod
Adding Dependencies
pnpm add react
pnpm add -D vitest @testing-library/react
pnpm add react@18.3.1
pnpm add lodash --filter @myapp/utils
Removing Dependencies
pnpm remove lodash
pnpm remove lodash --filter @myapp/utils
Updating Dependencies
pnpm update
pnpm update react
pnpm update react --latest
pnpm update --interactive
pnpm outdated
Running Scripts
pnpm run dev
pnpm run build
pnpm run test
pnpm run lint
pnpm dev
pnpm test
pnpm start
pnpm run build --filter @myapp/web
pnpm run build --recursive
pnpm -r build
One-Off Execution (dlx)
pnpm dlx create-vite my-app --template react-ts
pnpm dlx shadcn@latest add button
pnpm dlx tsc --noEmit
npx create-vite my-app
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
strict-peer-dependencies=true
auto-install-peers=true
shamefully-hoist=false
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:
shamefully-hoist=true
CI/CD Integration
GitHub Actions with pnpm
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-node with cache: 'pnpm' handles this.
- Pipeline order: typecheck → lint → test → build (fail fast on cheapest checks first).
Workspace Support (Monorepos)
Workspace Configuration
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
pnpm -r build
pnpm --filter @myapp/web build
pnpm --filter @myapp/web... build
pnpm add @myapp/ui --filter @myapp/web --workspace
pnpm add -D typescript -w
Cross-Package Dependencies
{
"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.
import something from 'phantom-dep';
Fix: Add the missing dependency explicitly:
pnpm add phantom-dep
Peer Dependency Conflicts
Problem: pnpm install fails with peer dependency errors.
pnpm install 2>&1 | grep "WARN"
pnpm update conflicting-package
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.
pnpm install
git diff pnpm-lock.yaml
git add pnpm-lock.yaml
Never delete pnpm-lock.yaml — it contains resolved versions that ensure reproducible builds.
Cache Issues
pnpm store prune
pnpm store status
rm -rf node_modules
pnpm install
Module Resolution Issues
pnpm list react
pnpm why react
pnpm list --depth=0
Common Mistakes
❌ Using npm/yarn in a pnpm Project
npm install lodash
yarn add lodash
pnpm add lodash
❌ Deleting Lock File
rm pnpm-lock.yaml
pnpm install
pnpm install
pnpm update affected-package
❌ Missing --frozen-lockfile in CI
- run: pnpm install
- run: pnpm install --frozen-lockfile
❌ Using npx Instead of pnpm dlx
npx create-vite my-app
pnpm dlx create-vite my-app
Summary
- ✅
pnpm for all package operations — never npm/yarn in pnpm projects
- ✅
pnpm run for scripts, pnpm dlx instead of npx
- ✅
pnpm-lock.yaml always committed
- ✅
--frozen-lockfile in CI
- ✅
.npmrc with strict peer deps and no shameful hoisting
- ✅
pnpm/action-setup@v4 for GitHub Actions
- ✅
workspace:* for monorepo cross-dependencies
- ✅ Fix phantom deps by adding explicit dependencies