Home JavaScript npm ERR! ERESOLVE: Fix Dependency Conflicts Fast
Intermediate 7 min · September 23, 2026

npm ERR! ERESOLVE: Fix Dependency Conflicts Fast

npm ERESOLVE means npm v7+ found conflicting peer dependencies, so read the report, run npm ls, and add overrides.

N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Everything here is grounded in real deployments.

Follow
Production
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
Before you start⏱ 14 min
  • Node.js with npm v7 or newer installed and checkable
  • A project with package.json plus package-lock.json
  • Basic comfort reading semver ranges like ^18.0.0
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Read the report first: While resolving X names the requester, Found Y names the installed copy, Could not resolve names the clash.
  • Run npm ls to see which parents pull in each duplicate, then npm dedupe to collapse copies that semver allows.
  • Pin one copy with the overrides field in package.json (e.g. react@18.2.0), then run npm install to rebuild the lockfile.
  • Treat --legacy-peer-deps as a temporary unblock only: it ignores peer clashes and can ship two Reacts or a broken plugin.
  • Commit package-lock.json and prove the fix with npm ci — never delete node_modules as a first step.
✦ Definition~90s read
What is npm ERESOLVE Dependency Conflict Fix?

npm ERESOLVE is the strict resolver's refusal to build a dependency tree that cannot satisfy every peer requirement at once. Since npm 7, the arborist resolver treats each peerDependencies range as a hard constraint across the whole tree: package A may demand react@17.x while your app provides 18.2.0, and if no single version meets all demands, the install halts with ERESOLVE before downloading anything.

Imagine two wedding guests who each demand the only window seat at the same table — guest A will not sit unless the seat is hers, guest B will not sit unless it is his, and there is one chair.

The report is structured as evidence — a While resolving line naming the requester, Found lines listing the versions already placed, and a Could not resolve line stating the unsatisfiable range — followed by a generic hint about --legacy-peer-deps that is an escape hatch, not advice.

Resolution always follows the same shape: map the duplicates with npm ls, collapse the harmless ones with npm dedupe, and settle the real clash by upgrading a package or pinning one copy with the overrides field, then rebuild package-lock.json with npm install. Overrides reach through transitive layers you never chose directly — a plugin three levels deep gets the pinned copy too. --legacy-peer-deps takes the opposite route: it skips peer checking entirely, restoring npm 6 behavior where installs succeed and incompatible or duplicated copies break at runtime.

Equally important is what ERESOLVE is not. It is not a network or registry error — retrying the identical command fails identically. It is not cache corruption — npm cache clean changes nothing about version constraints. It is not fixed by deleting node_modules, which removes downloaded files while the conflicting requirements sit untouched in package.json and the lockfile.

And it is not bad luck: npm majors resolve differently, so npm 6 installing what npm 9 refuses usually means version skew between machines, not a flaky registry.

Plain-English First

Imagine two wedding guests who each demand the only window seat at the same table — guest A will not sit unless the seat is hers, guest B will not sit unless it is his, and there is one chair. npm v7 is the planner who stops the wedding instead of squeezing both in. Older npm stacked them together and hoped, which is why apps shipped two React copies with bizarre bugs. ERESOLVE hands you the conflict card: read who wants what, pick one arrangement with overrides, and rewrite the seating chart.

You run npm install after adding one harmless package, and instead of a progress bar you get a wall of red: npm ERR! ERESOLVE could not resolve, While resolving X, Found Y, Could not resolve dependency. Nothing you wrote is wrong, yet the install refuses to proceed. If you have shipped JavaScript since npm 7, you have stared at this report — and if you have not, your first encounter is only one dependency away.

The frustration is understandable, but the error is doing you a favor. Before npm 7, conflicting peer demands were waved through with a warning, and apps shipped with two copies of React or a date picker wired to the wrong version. Those installs succeeded and then broke at runtime in ways nobody could trace. ERESOLVE fails loudly at install time so you fix the wiring before your users find it.

This guide teaches you to read the report like a work order: who demands what, which copies collide, and which fix fits. You will learn the npm ls plus dedupe workflow for mapping conflicts, the overrides field for pinning transitive copies, and the honest risks of --legacy-peer-deps. You will also learn the lockfile discipline that keeps conflicts fixed — including why deleting node_modules is the last resort, not the first step.

Why npm v7 Turned Peer Clashes Into Hard Errors

Peer dependencies are a package's way of saying I need to share one copy of something with your app. A plugin does not bundle its own React — it declares react as a peer and expects your project to provide exactly one. That design prevents duplicate stateful libraries, which break hooks, contexts, and singletons. Through npm 6, the installer treated peer clashes as advisory: it printed a warning, installed whatever fit best, and moved on. Most teams never read those warnings, so applications routinely shipped with doubled libraries and mystery runtime bugs.

npm 7 replaced the installer with arborist, a resolver that builds the entire dependency tree as one constraint problem before downloading anything. Every peer range becomes a hard requirement, and when no single version satisfies all of them, the install stops with ERESOLVE instead of guessing. This strictness felt like a regression to teams upgrading npm — installs that worked for years suddenly failed — but it converted silent runtime corruption into loud install-time failures. A red build you can read beats a green build that crashes in production.

The practical consequence is that peer ranges now deserve the same respect as your own dependencies. Before adding any package, inspect its peer demands with npm view and compare them against your installed majors. If a plugin wants React 17 and you run 18, you have three honest options: upgrade the plugin, downgrade your app (rarely wise), or pin the tree with overrides after verifying compatibility. What you cannot do anymore is ignore the mismatch and hope — arborist removed hope from the process, and your installs are better for it.

check-resolver.shBASH
1
2
3
4
5
6
7
8
9
# Which resolver are you even running? ERESOLVE strictness started with npm 7.
npm --version
node --version

# See what a suspect package demands before you install it
npm view eslint-config-react-app peerDependencies

# Dry-run an install to preview conflicts without touching node_modules
npm install --dry-run 2>&1 | head -40
📊 Production Insight
The Tuesday incident was pure npm 6 muscle memory: a developer expected the clash to warn-and-continue, but npm 9 stopped the release branch cold. Teams that learned the strict model now audit peer ranges before adding packages, not after releases fail.
🎯 Key Takeaway
Arborist made peer ranges hard constraints — loud install failures replaced silent duplicate-library runtime bugs.

Reading the ERESOLVE Report Line by Line

An ERESOLVE report looks intimidating, but it contains exactly three facts plus an advertisement. Fact one sits on the While resolving line: it names the package whose demand could not be satisfied, often your root project or a plugin you just added. Fact two sits on the Found lines: they list the versions already placed in the tree, with their locations — top level versus nested under some parent. Fact three sits on the Could not resolve line: it states the required range that nothing in the tree meets. Everything after that, including the suggestion to retry with --legacy-peer-deps, is npm's generic escape hatch, not a recommendation.

Read a React 17 versus 18 clash through this lens and it becomes a work order. While resolving: your-app names your project as the requester. Found: react@18.2.0 at the top level shows what you run. Found: react@17.0.2 nested under the analytics package shows the intruder. Could not resolve: react@17.x from the analytics plugin states a demand your tree cannot meet without demoting your whole app. The fix space is now obvious and small: make the plugin accept 18 (upgrade it), force one copy with overrides, or remove the plugin. None of that requires touching node_modules.

Build one reading habit and most reports surrender in minutes: scan upward from the bottom until you find the first While resolving line, then read downward through its Found and Could not resolve lines as a group. Reports can chain several groups when multiple clashes exist — resolve them one group at a time, starting with the shared libraries (react, typescript, eslint) whose duplicates cause the worst runtime damage.

⚠ The Suggested Flag Is Not a Suggestion
The --legacy-peer-deps hint at the bottom of every ERESOLVE report is a generic escape hatch, not advice. It resolves the red text by disabling the check that produced it — useful for unblocking your afternoon, dangerous as a committed default.
📊 Production Insight
In the Tuesday incident the answer sat in the report's first five lines for four hours while the team re-ran installs. The engineer who finally read it top-down named the analytics package in under a minute.
🎯 Key Takeaway
While resolving names the requester, Found lists the copies, Could not resolve states the demand — read them as one group.

npm ls and npm dedupe: Mapping the Conflict

Once the report names the clash, npm ls draws you a map of it. Running npm ls with the conflicted package name prints every installed copy and, critically, the chain of parents above each one — so you can see that react@18.2.0 sits at the top level while react@17.0.2 hides two levels deep under the analytics plugin. That parent chain is the whole game: it tells you whether the fix belongs in your direct dependencies or in an overrides entry aimed at a transitive one. Run npm ls before every fix attempt and after, so you have a before-and-after picture instead of vibes.

npm dedupe plays the supporting role. After hoisting analysis, npm can often collapse duplicate minor versions into one shared copy when their ranges overlap — three nested copies of a utility at 4.17.20 and 4.17.21 become one. Run it, then re-run npm ls to see what survived. Whatever remains after dedupe represents genuinely incompatible demands (different majors, disjoint ranges) that no hoisting trick can merge. That boundary matters because it tells you when to stop tidying and start deciding: upgrade a package, pin with overrides, or replace the offender.

Two companion commands round out the mapping kit. npm view with the peerDependencies field shows what any published version demands without installing it — perfect for checking whether a newer plugin release already supports your major. npm outdated flags how far your pins lag, which hints whether an upgrade path even exists. Together these turn dependency archaeology from guesswork into a fifteen-minute routine: map with ls, tidy with dedupe, research with view, and only then edit package.json.

map-conflict.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
# Map every copy and the parents pulling it in
npm ls react

# See exactly what a published version demands (no install needed)
npm view some-analytics-plugin peerDependencies
npm view some-analytics-plugin versions --json

# Collapse duplicates whose ranges overlap, then re-map
npm dedupe
npm ls react

# How far behind are the candidates for upgrade?
npm outdated react some-analytics-plugin
📊 Production Insight
Teams that skip the mapping step edit package.json blind and re-roll the error. The Tuesday fix took nine minutes because npm ls showed the nested React copy's exact parent before anyone touched a version number.
🎯 Key Takeaway
ls draws the parent chains, dedupe collapses what ranges allow, and whatever survives needs overrides or an upgrade.

The overrides Field: Pinning Your Way Out

The overrides field is package.json's way of settling transitive disputes by fiat: it forces one version of a package across your entire tree, no matter what nested dependencies request. When an analytics plugin demands react@17.x and your app runs 18.2.0, an override pinning react to 18.2.0 tells arborist to place that copy everywhere and skip the unsatisfiable demand. One edit, one npm install to rebuild the lockfile, and the resolver that failed for hours goes green. The snippet below shows the shape — an overrides block keyed by package name with the exact version you have already tested.

Overrides demand respect because they override compatibility claims you did not write. Pinning React 18 under a plugin built for 17 can work flawlessly or break subtly, so verify like you mean it: run the full test suite, exercise the plugin's UI paths in staging, and confirm with npm ls that exactly one copy remains. Prefer upgrading the requesting package first — a plugin release whose peer range already includes your major needs no forcing at all. Reach for overrides when the upgrade does not exist yet, the maintainer is slow, or the old peer range is needlessly narrow for code that works fine.

Keep overrides visible and temporary where possible. Comment each entry with why it exists and which upstream release would let you remove it, and re-check on every dependency upgrade pass. An overrides block with no comments becomes permanent furniture that nobody dares touch — the same trap as a committed --legacy-peer-deps flag, just better dressed. Review them quarterly: the best override is the one you delete because the ecosystem caught up.

package.jsonJSON
1
2
3
4
5
6
7
8
9
10
11
{
  "name": "your-app",
  "private": true,
  "dependencies": {
    "react": "18.2.0",
    "some-analytics-plugin": "3.4.0"
  },
  "overrides": {
    "react": "18.2.0"
  }
}
📊 Production Insight
The Tuesday fix pinned react to 18.2.0 with overrides and went green in nine minutes, but the entry carried a comment naming the plugin upgrade that would retire it. One sprint later that upgrade landed and the override was deleted on schedule.
🎯 Key Takeaway
Overrides force one version across the tree — verify with tests and npm ls, comment why, and retire them when upstreams catch up.

--legacy-peer-deps: What It Hides and When It Is Safe

The --legacy-peer-deps flag tells npm to resolve peers the way npm 6 did: install through the clash, print warnings nobody reads, and hope the tree works. As a local, temporary unblock it has legitimate uses — you need a build running this afternoon while the real fix waits on a plugin release. The danger starts when temporary becomes permanent: committed into .npmrc, baked into Dockerfiles, pasted into runbooks. At that point every future peer clash across every dependency installs silently, and your team loses the exact protection arborist was built to provide.

What it hides is concrete and nasty. Duplicate copies of stateful libraries — two Reacts, two emotion instances, two router contexts — where hooks fire against the wrong copy and contexts read as empty. A plugin wired to an incompatible major that throws only on the one code path your tests skip. None of this appears at install time because you disabled the check that would have named it. The Tuesday incident is the textbook case: the flag produced a green laptop install with react 17 nested under the plugin, and staging crashed with invalid hook calls that took longer to diagnose than the original conflict.

If you must use it, fence it in. Pass it on the command line for one install, never commit it, and open the tracking ticket in the same hour with the overrides-or-upgrade plan attached. Grep the repo for it in CI and fail builds that smuggle it into .npmrc without an expiry comment. And remember its honest alternative: --strict-peer-deps, which turns even warnings into errors so new clashes surface in pull requests instead of release branches.

legacy-flag-check.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
# Temporary local unblock ONLYdo not commit this
npm install --legacy-peer-deps

# Prove what the flag hid: look for duplicated singletons
npm ls react

# Find every place the flag might be smuggled in
cat .npmrc 2>/dev/null
grep -rn "legacy-peer-deps" .npmrc Dockerfile* .github/ 2>/dev/null

# Stricter alternative for CI: fail on warnings too
npm install --strict-peer-deps
📊 Production Insight
Staging's invalid-hook-call crash traced straight to a flag-masked duplicate React. The team now greps for legacy-peer-deps in CI and treats any hit as a blocking review comment, not a style nit.
🎯 Key Takeaway
The flag restores silent npm 6 behavior — fine for one fenced afternoon, dangerous as a committed default that hides duplicates.

Lockfile Discipline: Why Deleting node_modules Comes Last

The lockfile is the only record of the tree that actually worked, so treat it as a first-class artifact. package-lock.json pins every transitive version, which means npm ci on any machine rebuilds the identical tree that passed your tests. That determinism is exactly what ERESOLVE fixes need: after overrides or upgrades resolve the clash, npm install writes the settlement into the lockfile, you commit it, and every teammate plus CI installs the settled tree instead of re-litigating the conflict. A fix that is not in the lockfile is a rumor, not a resolution.

This is also why deleting node_modules fixes nothing. The conflict lives in version requirements — ranges in package.json and pins in the lockfile — while node_modules holds only downloaded files. Wiping the folder and reinstalling replays the same resolver against the same requirements and fails identically, ten minutes later. Reserve deletion for its real uses: corrupted downloads, phantom binaries after an interrupted install, or native modules built against the wrong Node version. Those are file problems with file solutions; ERESOLVE is a requirements problem with a requirements solution.

Build the discipline into your workflow and conflicts stop recurring. Commit the lockfile for every application (libraries may differ, apps must not). Prove fixes with rm -rf node_modules plus npm ci on a clean checkout before merging. Run npm ci — never bare npm install — in CI so a stale lockfile fails loudly instead of being silently rewritten. The teams that follow this routine spend dependency-upgrade days reviewing lockfile diffs calmly; everyone else spends release days re-running installs hopefully.

lockfile-discipline.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
# Rebuild the lockfile after editing package.json (overrides, ranges, upgrades)
npm install

# Review what the resolver actually settled before committing
git diff --stat package-lock.json

# Prove the settlement on a clean tree — this is what CI must run
rm -rf node_modules
npm ci

# Final assertion: exactly one copy of the conflicted singleton
npm ls react
🔥CI Must Use npm ci
Bare npm install in CI can silently rewrite your lockfile and mask the very conflict you just fixed. Use npm ci everywhere automated, and make lockfile diffs mandatory review material on dependency pull requests.
📊 Production Insight
The Tuesday post-mortem counted four node_modules deletions before anyone read the report. CI now runs npm ci exclusively, so a conflicting tree fails the pull request in minutes instead of blocking the release branch for hours.
🎯 Key Takeaway
The lockfile is the settlement — commit it, rebuild it with install, prove it with ci, and stop deleting folders hopefully.
● Production incidentPOST-MORTEMseverity: high

The Tuesday Build That Went Red Over a React Peer Clash

Symptom
The release branch failed npm ci with ERESOLVE on every run while laptops with --legacy-peer-deps installed cleanly. Staging, deployed from a flag-masked tree, threw invalid hook call errors on every page using the analytics widget. Deploys stayed blocked for 4 hours with the error signature identical each time.
Assumption
The team assumed the registry was having a bad morning, then assumed the lockfile had corrupted itself — so two engineers deleted node_modules, cleared caches, and re-ran the install four times. When a senior finally read the report instead of re-running it, the conflict had been named in the very first line all along.
Root cause
The analytics package declared a peer dependency of react@17.x while the application ran react@18.2.0, and npm 9's strict arborist resolver refused to build a tree satisfying both. A developer unblocked their laptop with --legacy-peer-deps, which installed react 17.0.2 nested under the analytics package alongside the top-level 18.2.0. The release branch running clean npm ci failed outright, and the flag-masked tree later crashed staging with an invalid hook call caused by the two React copies.
Fix
Three concrete changes closed it out. First, an overrides entry pinned react to 18.2.0 across the tree and npm install rebuilt the lockfile — npm ls react showed exactly one copy and CI went green in 9 minutes. Second, --legacy-peer-deps was removed from .npmrc and the analytics package was upgraded two minor releases to one whose peer range officially supports React 18, letting the override retire the next sprint. Third, CI gained an npm ci step on a clean checkout plus an npm ls react assertion, so a duplicate React fails the pull request instead of the release branch.
Key lesson
  • Read the resolver output before re-running the command. ERESOLVE names the requester, the installed copy, and the clash in its first lines. Four blind re-installs burned an hour; one careful read would have pointed at the analytics package in five minutes.
  • Convenience flags are debt with interest. --legacy-peer-deps bought a green local install and paid for it with a staging runtime crash nobody could trace. If a flag suppresses a safety check, the suppression needs an owner, a ticket, and an expiry date.
  • Assert your dependency invariants in CI, not in memory. A one-line npm ls assertion for singleton packages like react turns next quarter's peer clash into a failed pull request instead of a blocked release.
Production debug guideFive conflict scenarios with the exact npm commands that diagnose each one — capture output before changing files.5 entries
Symptom · 01
npm install fails with a wall of ERESOLVE output
Fix
Run npm install 2>&1 | tee /tmp/eresolve.log, then read the full report — not just the last line. Find the While resolving X line (the requester), the Found: Y lines (installed copies), and the Could not resolve line (the unsatisfiable demand). Those three lines are the entire work order; everything below is the suggested escape hatch, not the diagnosis.
Symptom · 02
You need to know which packages drag in the duplicates
Fix
Run npm ls react (substituting the conflicted package) to print every copy in the tree and which parent pulls it in. Then run npm view <parent> peerDependencies for the top suspect to see the exact range it demands. If the tree shows 18.2.0 at top level and 17.0.2 nested under one plugin, you have a transitive clash and the fix is overrides, not an upgrade of your app code.
Symptom · 03
The tree shows multiple copies that might collapse
Fix
Run npm dedupe and then npm ls <pkg> again. Dedupe hoists shared copies and collapses duplicates whose ranges overlap, which clears minor-version clutter for free. If two majors remain afterward, dedupe has done all it can — the leftover pair needs overrides or an upgrade, and no further dedupe runs will change that.
Symptom · 04
You have picked a winning version and must enforce it
Fix
Add an overrides entry pinning the shared package, run npm install, and review the lockfile diff for sanity. Then prove the tree is clean: npm ls <pkg> must show exactly one version, and rm -rf node_modules && npm ci on a fresh checkout must pass. If CI still fails here, the committed lockfile is stale — rebuild and commit it.
Symptom · 05
CI fails but a teammate's laptop installs fine
Fix
Run npm --version on both machines and compare majors — npm 6 waves through clashes that npm 9 refuses, so version skew alone explains most cannot-reproduce-it cases. Standardize on one npm major via the Node image or packageManager field, delete the flag from .npmrc with grep -rn legacy-peer-deps .npmrc .npmrc* 2>/dev/null, and re-resolve with the standard version.
ERESOLVE Causes Compared — Confirm Before You Fix
Root CauseHow to ConfirmFixPrevention
Direct peer clash on major versionsReport shows While resolving your app with two Found lines on different majorsUpgrade one side or pin with overridesCheck peer ranges before adding any dependency
Transitive dependency drags an old peernpm ls <pkg> shows the old copy nested under one parentAdd overrides for the nested packageRun npm ls on shared deps after each new install
Stale lockfile disagrees with package.jsonFresh clone plus npm ci fails while old checkout installsRun npm install to rebuild the lock, commit it, verify with npm ciAlways commit the lockfile; never gitignore it in apps
npm version skew across machinesnpm --version differs; npm 6 installs what npm 9 refusesStandardize the npm major; re-resolve with one versionPin Node/npm in CI image and document it
--legacy-peer-deps masking real clashesInstall succeeds but npm ls shows duplicates; runtime breaksRemove the flag; resolve each clash with overridesForbid the flag in .npmrc; allow it only as a timed exception
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
check-resolver.shnpm --versionWhy npm v7 Turned Peer Clashes Into Hard Errors
map-conflict.shnpm ls reactnpm ls and npm dedupe
package.json{The overrides Field
legacy-flag-check.shnpm install --legacy-peer-deps--legacy-peer-deps
lockfile-discipline.shnpm installLockfile Discipline

Key takeaways

1
ERESOLVE means npm v7+ found peer demands no single version can satisfy
read the report before reaching for flags.
2
While resolving names the requester, Found names the installed copy, Could not resolve names the clash.
3
Map conflicts with npm ls <pkg>, collapse harmless duplicates with npm dedupe, pin real clashes with overrides.
4
--legacy-peer-deps restores silent npm 6 behavior and can ship broken peers
use it as a timed exception, never a default.
5
The lockfile is the fix record
commit it, rebuild it with npm install, and prove it with npm ci.
6
Deleting node_modules cannot fix a version conflict
the clash lives in requirements, not downloaded files.

Common mistakes to avoid

6 patterns
×

Deleting node_modules as the first step

Symptom
rm -rf node_modules plus a fresh install takes ten minutes, then fails with the identical ERESOLVE report — because the conflict lives in version requirements, not in downloaded files. The ritual burns time on every recurrence without ever addressing the cause.
Fix
Read the report first: npm install 2>&1 | tee /tmp/eresolve.log, then identify the requester, the installed copy, and the clash. Only then run npm ls <pkg> to map the conflict. Diagnosis takes five minutes; blind flag-adding takes hours.
×

Committing --legacy-peer-deps into .npmrc permanently

Symptom
Builds go green while peer clashes pile up silently. Months later a plugin breaks at runtime in production with no install-time warning left to explain it, and nobody remembers which dependency pair was even in conflict.
Fix
Keep --legacy-peer-deps as a local, temporary unblock and track the real fix in the backlog the same day. If CI needs it to stay green, that is a signal to schedule the overrides or upgrade work now, not to commit the flag.
×

Ignoring peer warnings until they become errors

Symptom
npm prints a wall of peer warnings for months that everyone scrolls past. After an npm upgrade or a transitive bump, one of those warnings hardens into ERESOLVE and blocks the release branch on the worst possible day.
Fix
Run npm view <pkg> peerDependencies to see what the package actually demands, then satisfy it with overrides or an upgrade. Treat peer warnings as errors in CI with --strict-peer-deps so new clashes surface at PR time.
×

Hand-editing package-lock.json to force a resolution

Symptom
The next npm install silently rewrites your manual edit, the conflict returns, and the diff is unreadable. Worse, a hand-edited lockfile can describe a tree npm itself would never build, so teammates get different installs from the same commit.
Fix
Let npm rewrite it: change package.json (overrides, ranges, upgrades) and run npm install, then review the lockfile diff. The lockfile is generated output — your edits belong in package.json.
×

Mixing npm versions across the team

Symptom
The conflict reproduces on one laptop but not another, and CI disagrees with both. Half the team cannot reproduce the bug because npm 6 silently installed through the clash that npm 9 refuses, so every debugging session starts with version archaeology.
Fix
Standardize the npm major version in CONTRIBUTING and CI (npm --version assertion or a pinned Node image), and use overrides for transitive clashes rather than flags that differ by version. Verify with npm ci on a clean checkout before merging.
×

Upgrading only the top-level package and leaving the transitive clash

Symptom
The direct dependency moves on but the old peer demand rides along underneath it, so ERESOLVE returns on the next install. Version bumps without a lockfile diff review just relocate the conflict instead of resolving it.
Fix
Use overrides to pin the transitive copy to the version your app already uses (e.g. react@18.2.0), or upgrade the requesting package to a release with wider peer support. Then confirm with npm ls react that exactly one copy remains.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Why did ERESOLVE appear after upgrading npm?
Q02JUNIOR
How do you read an ERESOLVE report?
Q03SENIOR
What does --legacy-peer-deps do and what are its risks?
Q04SENIOR
How do overrides fix a transitive peer conflict?
Q05SENIOR
CI fails with ERESOLVE but local install passes — how do you debug it?
Q01 of 05JUNIOR

Why did ERESOLVE appear after upgrading npm?

ANSWER
npm 6 installed peer dependencies leniently and printed warnings, so conflicting peers slipped through silently. npm 7 introduced the arborist resolver that builds the whole tree strictly and fails the install with ERESOLVE when no version satisfies every peer demand. The strictness is intentional: silent duplicates caused runtime bugs like two React copies.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Should CI use npm install or npm ci?
02
Is --legacy-peer-deps ever acceptable?
03
What does the overrides field actually do?
04
Will deleting node_modules fix ERESOLVE?
05
Does npm dedupe resolve peer conflicts?
06
Why do peer conflicts keep coming back?
N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Everything here is grounded in real deployments.

Follow
Verified
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
🔥

That's Node.js. Mark it forged?

7 min read · try the examples if you haven't

Previous
ECONNREFUSED Connection Refused Fix
21 / 30 · Node.js
Next
ReferenceError Not Defined Fix