When to Use peerDependencies vs devDependencies
Exact Symptoms
npm WARN unmet dependency/peer dependency not satisfiedModule not found: Can't resolve 'react' in '...'(or equivalent host framework)ERESOLVE unable to resolve dependency tree- Duplicate
node_modulestrees, bloated consumer bundles, or silent runtime API mismatches
Root Cause Analysis
Package managers strictly separate build-time tooling from runtime host requirements. devDependencies are ephemeral and explicitly stripped during production installs (npm install --omit=dev). peerDependencies enforce a strict contract requiring the consuming application to provide a specific dependency version. Misclassification occurs when library authors place runtime host libraries (e.g., React, Angular, Webpack) in devDependencies, causing missing module errors in consumer environments. Conversely, declaring internal tooling (linters, bundlers, test runners) as peerDependencies triggers strict resolution conflicts, duplicate module trees, and installation halts when version ranges misalign with the consumer's lockfile. Mastering the mechanics of Dependency Resolution Explained is critical for preventing these pipeline failures.
Resolution & CLI Debugging
- Audit Usage: Separate runtime host requirements from build/test tooling. Host frameworks belong in
peerDependencies; linters, bundlers, and test runners belong indevDependencies. - Refactor
package.json: Move host-environment packages topeerDependencieswith appropriate semver ranges. Remove duplicate entries fromdevDependenciesto prevent version collisions and bundle bloat. - Configure Metadata: Add
peerDependenciesMetato mark non-critical peers as optional, preventing hard installation failures when consumers lack specific integrations. - Monorepo Overrides: Use workspace protocols (
workspace:*) or configure package manager overrides to bypass strict peer checks during local development. - Isolated Validation: Test installation in a fresh consumer project to verify zero unmet peer warnings and correct runtime module resolution.
Diagnostic CLI Commands
# Diagnose unmet peers and tree conflicts
npm ls --all
npm explain <package-name>
# Temporary bypass for local development (use sparingly)
npm install --legacy-peer-deps
pnpm install --strict-peer-dependencies=false
# Validate clean install in an isolated consumer environment
mkdir test-consumer && cd test-consumer
npm init -y && npm install ../your-library
npm ci # Verify zero warnings and correct module resolution
Required Configuration Patch
{
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
},
"devDependencies": {
"typescript": "^5.0.0",
"vitest": "^1.0.0"
}
}
Prevention Strategy
Automate dependency classification using CI linting rules (e.g., package-json-lint) and enforce peer dependency validation before publishing to registries. Document required host versions explicitly in README.md and use semantic versioning to signal breaking peer requirement changes. Integrate these validation gates into your Core JavaScript Package Workflows pipeline to catch misclassifications before they reach production consumers.
FAQ
Should I duplicate peerDependencies in devDependencies for local testing?
No. Modern package managers (npm v7+, yarn, pnpm) automatically install peerDependencies during local development. Duplicating them causes version resolution conflicts and inflates node_modules.
What happens if a consumer ignores a peer dependency warning?
The package will install but may crash at runtime with Module not found or exhibit silent API mismatches if the consumer's installed version lacks features your library expects.
How do I handle peer dependencies in a monorepo workspace?
Use workspace protocols (e.g., workspace:*) to link local packages, and configure your package manager's peer dependency rules (pnpm.peerDependencyRules, npm overrides) to allow flexible resolution without triggering strict version checks during development.
When should I use peerDependenciesMeta?
Use it to declare optional peer dependencies when your library supports multiple host environments or provides graceful fallbacks, preventing installation failures for consumers who don't need specific integrations.