Dependencies
Dependency management problems, version conflicts, and package management strategies
Dependency Troubleshooting
Dependency management issues can silently introduce bugs, security vulnerabilities, and build failures. This guide covers common problems and systematic solutions.
1. Version Conflicts — "Works on My Machine"
Problem
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! peer dep missing: react@^17.0.0, required by some-library@2.3.4
npm ERR! Found: react@18.2.0- Different team members get different
node_modules - Library X requires React 17, but project uses React 18
- Transitive dependency conflicts
Root Cause
- Lock file not committed or inconsistent
- Using
npm installinstead ofnpm ciin CI - Incompatible peer dependency requirements
Solution
Always commit lock files:
# Ensure lock file is committed
git add package-lock.json # npm
git add pnpm-lock.yaml # pnpm
git add yarn.lock # yarnUse npm ci in CI/CD (not npm install):
# CI pipeline
steps:
- run: npm ci # Installs exactly from lock file — deterministicResolve peer dependency conflicts:
// package.json — npm overrides
{
"overrides": {
"some-library": {
"react": "^18.0.0"
}
}
}// package.json — pnpm overrides
{
"pnpm": {
"overrides": {
"react": "^18.0.0"
}
}
}2. Phantom Dependencies — Accessing Undeclared Packages
Problem
// This works locally because `lodash` is a transitive dependency
// But breaks when the parent dependency stops using lodash
import _ from 'lodash'; // Not in package.json!Root Cause
npm and Yarn Classic use flat node_modules — you can import any package installed anywhere in the tree, even if you didn't declare it.
Solution
Use pnpm (strict by default):
# pnpm creates a strict node_modules structure
# Only declared dependencies are accessible
npm install -g pnpm
pnpm installOr enable strict mode in npm/Yarn:
// .npmrc
strict-peer-dependencies=true
// Yarn Berry (.yarnrc.yml)
nodeLinker: pnpm # Strict node_modules layoutAudit for phantom dependencies:
# depcheck finds undeclared and unused dependencies
npx depcheck
# Output:
# Missing dependencies:
# lodash: used in src/utils.ts but not in package.json
# Unused dependencies:
# moment: declared but never imported3. Security Vulnerabilities in Dependencies
Problem
npm audit
# found 15 vulnerabilities (3 moderate, 8 high, 4 critical)Solution
Automated vulnerability scanning:
# npm built-in audit
npm audit
npm audit fix # Auto-fix compatible updates
npm audit fix --force # Breaking changes — review carefully
# Better: use dedicated tools
npx better-npm-audit auditGitHub Dependabot (automated PRs for updates):
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
groups:
production:
patterns:
- "*"
exclude-patterns:
- "@types/*"
- "eslint*"
dev:
patterns:
- "@types/*"
- "eslint*"
open-pull-requests-limit: 10Pin critical dependencies:
{
"dependencies": {
"react": "18.2.0", // Exact version for framework
"next": "14.1.0" // Exact version for framework
},
"devDependencies": {
"@types/react": "^18.2.0" // Range OK for type definitions
}
}4. Bundle Size Bloat from Dependencies
Problem
A single dependency import pulls in an unexpectedly large amount of code:
import { format } from 'date-fns'; // Pulls in 200KB+ if not tree-shaken
import moment from 'moment'; // 330KB minifiedSolution
Check package size before installing:
# Check bundle impact
npx bundlephobia moment
# moment@2.29.4: 330kB min, 72.5kB min+gzip
npx bundlephobia date-fns
# date-fns@3.3.1: 78.2kB min (tree-shakeable to ~5kB for single function)Import analysis in CI:
// package.json
{
"scripts": {
"size": "size-limit",
"size:check": "size-limit --ci"
},
"size-limit": [
{ "path": "dist/index.js", "limit": "50 kB" },
{ "path": "dist/vendor.js", "limit": "150 kB" }
]
}Use Import Cost VS Code extension to see package size inline.
5. Dependency Update Breaking Changes
Problem
npm update
# Everything breaks because a minor version had breaking changes
# Or: updating one package cascades updates to 50 othersSolution
Update strategy — staged approach:
# 1. Check what's outdated
npm outdated
# 2. Update patch versions only (safest)
npm update --save
# 3. Check for major updates separately
npx npm-check-updates
# 4. Update major versions one at a time
npx npm-check-updates -u --target minor # Minor + patch only
npx npm-check-updates -u -f react # Single packageAutomated testing after updates:
# GitHub Actions — test after Dependabot PR
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run typecheck
- run: npm test
- run: npm run build
- run: npx playwright test # E2E tests catch UI regressionsRenovate for advanced update management:
// renovate.json
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"automerge": true
},
{
"matchUpdateTypes": ["major"],
"labels": ["breaking-change"],
"automerge": false
},
{
"groupName": "testing",
"matchPackagePatterns": ["jest", "vitest", "@testing-library"]
}
]
}6. Monorepo Dependency Issues
Problem
- Duplicate dependencies across packages
- Version mismatches between workspace packages
- Slow install times in CI
- Hoisting conflicts
Solution
Workspace dependency management:
// pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'// Root package.json — shared dependencies
{
"pnpm": {
"overrides": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.3.0"
}
}
}Workspace protocol for internal packages:
// apps/web/package.json
{
"dependencies": {
"@myorg/ui": "workspace:*", // Always uses local version
"@myorg/utils": "workspace:^1.0.0" // Semver within workspace
}
}Turborepo for cached builds:
// turbo.json
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["build"]
},
"lint": {}
}
}7. Lock File Merge Conflicts
Problem
Every PR touches package-lock.json or yarn.lock, causing painful merge conflicts.
Solution
For npm — auto-resolve with npm install:
# After merge conflict in package-lock.json
git checkout --theirs package-lock.json
npm install # Regenerates lock file from merged package.json
git add package-lock.jsonGit merge driver for lock files:
# .gitattributes
package-lock.json merge=npm-merge-driver
pnpm-lock.yaml merge=pnpm-merge-driver# Install the merge driver
npx npm-merge-driver installReduce conflicts — batch dependency updates:
// renovate.json — group updates to reduce PRs
{
"packageRules": [
{
"groupName": "all non-major",
"matchUpdateTypes": ["minor", "patch"],
"schedule": ["before 8am on Monday"]
}
]
}Summary: Dependency Management Best Practices
| Practice | Tool/Approach |
|---|---|
| Lock file consistency | Commit lock files, use npm ci in CI |
| Strict dependencies | pnpm (default), or npm with .npmrc |
| Security scanning | npm audit, Dependabot, Snyk |
| Bundle size control | bundlephobia, size-limit, Import Cost |
| Update strategy | Staged updates, automated testing, Renovate |
| Monorepo management | pnpm workspaces, Turborepo caching |
| Merge conflicts | npm-merge-driver, batched updates |
| Unused deps cleanup | depcheck, knip |