npm ERR! ERESOLVE: peer dependency conflicts — the complete fix guide
Symptom: npm install refuses to run
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: my-app@1.0.0
npm ERR! Found: react@18.2.0
npm ERR! node_modules/react
npm ERR! react@"18.2.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^17.0.0" from some-lib@2.1.0
npm 7+ enforces peer dependency resolution during install. Older versions just warned — now it's a hard stop.
What are peer dependencies (and why npm cares)
A peer dependency says: "I need this package to exist in the host app, but I won't install it myself." It exists so plugins and libraries share a single copy of the host framework instead of bundling their own.
When two packages disagree on the peer version (one wants React 17, another wants React 18), npm can't satisfy both → ERESOLVE error. This is not a bug — it's npm telling you there's a real compatibility risk.
Four resolution strategies (in order of safety)
Strategy 1: Fix the actual version conflict
Read the error carefully — it tells you exactly which package wants which version. Often the fix is simply upgrading (or downgrading) one of your direct dependencies so all peer ranges overlap:
# See what your current versions are
npm ls react
# Update the conflicting package
npm install some-lib@latest
Strategy 2: --legacy-peer-deps (quick unblock)
npm install --legacy-peer-deps
This tells npm to behave like npm 6: install anyway, warn about conflicts but don't block. Trade-off: the incompatibility is real — the conflicting packages might crash at runtime.
Make it permanent in .npmrc (project root):
legacy-peer-deps=true
When to use: you know the peer conflict is harmless (the library works fine with a newer version despite declaring an older peer range). Very common with React — many libs declare peer: react@^17 but work perfectly on 18.
Strategy 3: overrides in package.json (npm 8.3+)
Force a specific version for a transitive dependency:
{
"overrides": {
"some-lib": {
"react": "^18.2.0"
}
}
}
This tells npm: "I know some-lib wants React 17, but I'm overriding that to React 18." More precise than legacy-peer-deps.
Strategy 4: Manual resolution with dedupe
npm dedupe
Reduces duplication in node_modules by flattening compatible versions. Sometimes resolves conflicts by consolidating to a single version that satisfies all ranges.
When NOT to use --force
npm install --force is different from --legacy-peer-deps. Force installs everything regardless of conflicts, potentially creating multiple versions of the same package. This can lead to:
- Two Reacts in
node_modules→ hooks break ("Invalid hook call") - Type incompatibilities at runtime
- Unpredictable behavior
Never use --force on a project with React hooks — multiple React copies is the #1 cause of "Invalid hook call" errors.
Debugging tools
# See the dependency tree
npm ls react
# See why a package is installed
npm explain some-lib
# Check for duplicate versions
npm ls react | grep -c "react@"
Prevention
- Update regularly — small version bumps are easier than big jumps
- Use
npm outdatedto see what's behind - Read peer ranges before adding a library —
npm info some-lib peerDependencies
Reading an ERESOLVE message: the anatomy that matters
The error block looks intimidating; three fields carry the signal: the peer requirement (package X wants React 17), the found version (the tree has React 18), and the dependency path (who pulled X — this tells you which of your choices created the conflict). The fix decision follows from the path: if X is your direct dependency — downgrade to a version whose peer range matches your stack (the cleanest fix), find an alternative package, or override; if X arrived transitively — the parent package chose it, and your options are overrides or replacing the parent. The --force flag skips the check (installs with the mismatch — sometimes fine, occasionally runtime-breaks) and --legacy-peer-deps ignores peer ranges entirely (the npm 6 behavior — the "make it go away" that defers the same conflict to runtime, where it costs more to diagnose).
Overides: the surgical tool people reach for last
package.json overrides force a version deep in the tree — the correct tool when a transitive dependency pins something incompatible and you have verified the newer version works: overrides cut both ways (forcing a package to a version its parent did not test against). The discipline that keeps overrides safe: each override gets a comment with the reason and an expiry review date (the conflict that justified it may be resolved upstream — overrides outlive their purpose silently), and overrides stay minimal (override the specific package, not a wildcard). The pattern for monorepos: workspace-level resolutions (yarn) / overrides (npm) centralized once, not scattered per-package.
When conflicts mean "this stack is stuck"
Some ERESOLVE knots are symptoms, not problems: a UI library requiring React 17 while the platform is on 18 and its maintainers are inactive — the override installs, but you now own the compatibility risk forever (every React patch could break the untested pairing). The honest audit: how far behind is the blocking package (a release with updated peer ranges in the pipeline = wait), how central is it (replaceable tool vs the design system), and is the team staffed to own an override permanently (if not, replacement now is cheaper than an outage later). Dependency conflicts are technical debt with a compounding interest rate — the override is borrowing, not paying down.
FAQ
Why did this start happening after upgrading npm?
npm 7+ (released 2020) changed peer dependency handling from warn-only to hard-enforce. Projects that installed fine on npm 6 hit ERESOLVE on npm 7+.
Can I just delete package-lock.json and retry?
Sometimes works, but dangerous: it allows all transitive dependencies to float to their latest versions, potentially introducing new breaking changes. Only do this if you're also updating all direct dependencies.
What's the difference between dependencies, devDependencies, and peerDependencies?
dependencies: "I need this to run, install it for me"devDependencies: "I need this to build/test, install it for me"peerDependencies: "I need the HOST APP to have this installed — I won't install it, but I require it at runtime"
Triage notes
The four strategies are explicitly ranked by safety: fixing the actual versions is the correct move, while legacy-peer-deps is only a quick unlock — it defers the conflict to runtime, where diagnosis is more expensive. --force gets its own warning section because force unconditionally installs multiple copies of the same package, and duplicate React copies are the number-one cause of Invalid hook call — an essential difference from legacy-peer-deps. 'Reading an ERESOLVE message' ties the fix decision to the dependency path: if X is a direct dependency, downgrade or swap it; if it arrives transitively, an override is all that is left — which is also why overrides are called a scalpel and required to carry a reason comment and an expiry-review date.
Provided by ToolVault. Related: npm registry mirror guide, Cannot find module fix. See the homepage for more developer tools.
Related Tools
Related Articles
Linux Crontab in Practice: Commands, Logs, and Debugging
Crontab beyond syntax: three must-know commands, /etc/cron.d vs user crontabs, five-step debugging, PATH/timezone/% escaping, MAILTO, and what to do when you need second-level scheduling.
docker run to docker-compose: The Complete Migration Guide (with Flag Map)
Migrate docker run commands to docker-compose.yml: five advantages of compose over raw run, a flag-by-flag mapping table (ports, volumes, env, restart, networks), the classic pitfalls (bare -e passthrough, anonymous volumes, --rm semantics), and an online converter that runs locally in your browser.
Permission denied (publickey): 6 Reasons Git Push Fails Over SSH (and the Fix for Each)
git clone or push rejected with Permission denied (publickey) fatal: Could not read from remote repository? Covers missing keygen, key not loaded in the agent, public key not added to GitHub/GitLab, multi-account key routing with ~/.ssh/config, deploy key limits, and wrong remote URLs — with ssh -v diagnostics.