npm ERR! code ERESOLVE unable to resolve dependency tree: 9 Causes and Fixes
What npm Is Actually Telling You
npm ERR! code ERESOLVE unable to resolve dependency tree means npm found a peer dependency conflict it refuses to guess its way out of. Two packages in your tree each demand a version of the same dependency, and those demands do not overlap.
It is not a network error, a corrupt cache, or a broken npm install. The tree you asked for is genuinely unsatisfiable as written.
The reason this appeared in projects that installed fine for years is that npm 7 started enforcing peer dependencies instead of silently ignoring them. Nothing about your project got worse; the check got stricter.
Below are the nine situations that produce it, most common first, with the fix for each and how to confirm it worked.
| Error | npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! While resolving: my-app@1.0.0
npm ERR! Found: react@18.2.0
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^17.0.0" from some-package@3.1.0 |
|---|---|
| Where it happens | npm 7 or newer, running npm install or npm ci in a project with transitive peer dependencies - most often a React, Angular, or TypeScript toolchain |
| What it means | One package in your tree requires an older (or newer) version of a shared dependency than the one you have installed, and npm will not install a tree it knows is inconsistent. |
The Fast Fix
Read the four lines after Could not resolve dependency. They name the package and the version range it wants, and the version you actually have.
Most of the time the fix is upgrading that package to a release that supports your version:
npm install some-package@latest
If you need to unblock a build right now, npm install --legacy-peer-deps restores the pre-npm-7 behaviour of ignoring peer conflicts. It installs, it usually runs, and it hides a real incompatibility - so treat it as a stopgap and put the upgrade on the board.
What Is Actually Causing It
Jump to your case
- 1. A dependency has not published support for your major version yet
- 2. Two packages need different majors of the same peer
- 3. A stale package-lock.json from an older npm
- 4. Node and npm versions differ between your machine and CI
- 5. The conflict is in a transitive dependency you do not control
- 6. A dependency range in package.json that cannot be satisfied
- 7. Mixing package managers in one repository
- 8. Installing a package that was published with an over-narrow peer range
- 9. A monorepo workspace hoisting conflict
1. A dependency has not published support for your major version yet
Reproduce it
You upgraded React, Vue or Angular to a new major, then ran npm install. A plugin or component library still declares a peer range for the previous major.
Why it happens — Peer dependencies are the package author's statement about what they were tested against. Until they publish a release widening that range, npm treats your tree as unsatisfiable.
The fix
Check for a newer release with npm view some-package versions --json and npm view some-package@latest peerDependencies. If a supporting version exists, install it. If it does not, either stay on the previous major of the framework or replace the package.
Confirm it worked — npm install completes with no ERESOLVE block, and npm ls some-package prints the tree without invalid markers.
2. Two packages need different majors of the same peer
Reproduce it
Package A requires react@^17, package B requires react@^18. Both are direct dependencies of your app.
Why it happens — npm can duplicate ordinary dependencies at different versions, but a peer dependency is a request to share the single copy the host provides. Two incompatible requests for one shared instance cannot both be honoured.
The fix
Decide which package is load-bearing and upgrade or replace the other. If both are essential and neither will move, you are choosing between --legacy-peer-deps and dropping one of them - there is no correct install here.
Confirm it worked — npm ls react shows one version at the root and no invalid entries. If two copies of a framework end up in the tree, expect runtime errors like duplicate hook or context instances.
3. A stale package-lock.json from an older npm
Reproduce it
The lockfile was generated with npm 6, the repo has since moved to npm 8 or later, and npm ci now fails where it used to pass.
Why it happens — Lockfile version 1 does not record peer dependency relationships. Modern npm re-resolves them and finds conflicts the old lockfile never encoded.
The fix
Regenerate it deliberately: delete node_modules and package-lock.json, then run npm install once on the npm version your team standardises on, and commit the new lockfile.
rm -rf node_modules package-lock.json
npm install
Confirm it worked — The new lockfile has "lockfileVersion": 3 (or 2), and npm ci succeeds from a clean checkout. Confirm on CI, not only locally.
4. Node and npm versions differ between your machine and CI
Reproduce it
It installs on your laptop and fails in the pipeline, or the reverse. Nobody has pinned the toolchain.
Why it happens — Peer resolution behaviour changed across npm majors - npm 6 ignored conflicts, npm 7 enforced them, later versions adjusted the heuristics. Same package.json, different verdict.
The fix
Pin the toolchain in the repo. Add an engines field, commit an .nvmrc, and use the same Node image in CI as developers run locally.
{
"engines": { "node": ">=20", "npm": ">=10" }
}
Confirm it worked — node -v and npm -v print the same values locally and in the CI log. The install either passes in both places or fails in both - either result is progress over an inconsistent one.
5. The conflict is in a transitive dependency you do not control
Reproduce it
The error names two packages you have never installed directly. They arrived as dependencies of something else.
Why it happens — Peer requirements propagate up the tree. A conflict several levels down still has to be satisfied by the single shared copy at the root.
The fix
Find the path with npm ls <package>, which prints who required it. Then upgrade the direct dependency that pulls in the offending version. If the maintainer is unresponsive, overrides in package.json forces a version across the tree:
{
"overrides": { "some-package": "3.2.0" }
}
Confirm it worked — npm ls <package> shows the single expected version. Because overrides bypass a constraint an author declared, run your test suite before trusting it - this is the fix most likely to move a failure from install time to runtime.
6. A dependency range in package.json that cannot be satisfied
Reproduce it
Someone pinned an exact version ("react": "17.0.2") while another dependency requires ^18, or a range was hand-edited without running an install.
Why it happens — An exact pin removes npm's room to find a version satisfying everyone. With no overlap between the ranges, resolution fails immediately.
The fix
Relax the pin to a caret range unless there is a documented reason for the exact version. If the pin exists to work around a known bug, record that reason in a comment or an issue link so the next person does not simply widen it again.
Confirm it worked — npm install resolves, and npm ls shows a version that satisfies every declared range. Re-run the test that motivated the pin, if one exists.
More causes (3 remaining)
7. Mixing package managers in one repository
Reproduce it
The repo contains both package-lock.json and yarn.lock, or someone ran pnpm install once. Different developers get different trees.
Why it happens — Each manager resolves peer dependencies with different rules - yarn and pnpm have historically been more permissive than npm 7+. The lockfiles then disagree about what a valid tree is.
The fix
Pick one manager, delete the other lockfiles, and enforce it. A preinstall guard makes the decision hard to ignore:
{
"scripts": { "preinstall": "npx only-allow npm" }
}
Confirm it worked — Exactly one lockfile exists in the repo, and a clean clone plus install succeeds for a second developer. Ask someone else to try - your machine has state that a fresh checkout does not.
8. Installing a package that was published with an over-narrow peer range
Reproduce it
A small or infrequently maintained package declares peerDependencies pinned to a single minor version, and your project is one minor ahead.
Why it happens — The range is the author's declaration, not a technical limit. An over-narrow range fails resolution even when the package would work fine.
The fix
Check the repository for an open issue or a newer release first. If the package genuinely works, --legacy-peer-deps for that install, or an overrides entry, are the pragmatic options - and opening a PR widening the range is the fix that helps everyone.
Confirm it worked — The install succeeds and the package's own functionality works in your test suite. This is one of the few cases where bypassing the check is defensible, but only after you have exercised the code path.
9. A monorepo workspace hoisting conflict
Reproduce it
Two workspaces in the same repo depend on different majors of a shared library, and the root install fails even though each package installs on its own.
Why it happens — npm workspaces hoist shared dependencies to the root node_modules. A single hoisted copy cannot satisfy two incompatible peer requirements from sibling workspaces.
The fix
Align the versions across workspaces - that is usually the real fix and the reason the monorepo exists. If one workspace must stay behind temporarily, declare the dependency in that workspace's own node_modules by pinning it there and accepting the duplication.
Confirm it worked — npm ls <package> --workspaces shows the versions you expect per workspace, and a clean npm install at the root succeeds. Build every workspace, not just the one you changed.
- Read the
Could not resolve dependencyblock and write down three facts: which package demands the peer, the range it demands, and the version currently found. Most fixes follow directly from those three. - Run
npm ls <package>for the conflicting dependency. If the demanding package is not in yourpackage.json, the conflict is transitive and cause 5 applies. - Check
npm view <package>@latest peerDependencies. If the latest release supports your version, the fix is an upgrade, not a flag. - Compare
node -vandnpm -vwith your CI log. Different values mean you may be debugging a toolchain difference rather than a dependency problem. - Look at
lockfileVersionin package-lock.json. A value of 1 means the lockfile predates peer enforcement and cause 3 is likely. - List the repo root for stray lockfiles:
ls *.lock package-lock.json yarn.lock pnpm-lock.yaml. More than one rules cause 7 in. - Try
npm install --dry-runto see the resolution attempt without writing anything. It fails the same way but leaves node_modules untouched while you experiment.
Why npm Refuses Instead of Guessing
Ordinary dependencies are easy for npm: if two packages need different versions of the same library, it installs both, nested, and each gets what it asked for. Duplication is cheap and nobody notices.
Peer dependencies exist precisely because duplication is not always acceptable. When a plugin declares React as a peer, it is saying: do not give me my own copy, give me the one the application is using. React's hooks and context are tied to a module instance - two copies in one process produce errors that look like nothing in your code, such as hooks being called outside a component that is plainly a component.
So a peer dependency is a request to share one instance, and that is why conflicting requests cannot be resolved by nesting. There is only one shared copy, and it either satisfies every declared range or it does not.
Before npm 7, npm printed a warning and installed anyway, leaving you to discover the mismatch at runtime. npm 7 moved that discovery to install time. The error feels like a regression because a command that used to succeed now fails, but the underlying inconsistency was always there.
That framing tells you how to read --legacy-peer-deps honestly. It does not fix anything - it restores the old behaviour of installing a tree npm knows is inconsistent. Sometimes that is the right call, because the range was over-narrow and the code works. Often it converts a clear install-time failure into an obscure runtime one, which is a much worse trade than it looks like at 6 p.m. on a Friday.
Stop It From Coming Back
- Commit the lockfile and use
npm ciin CI. It installs exactly what the lockfile says and fails loudly when package.json and the lockfile disagree. - Pin the toolchain with an
enginesfield and an.nvmrc, and use the same Node version in CI as locally. - Upgrade framework majors and their plugin ecosystem in one deliberate change, not opportunistically alongside a feature.
- Keep
--legacy-peer-depsout of scripts and Dockerfiles. Once it is baked into the build, the underlying conflict is invisible until something breaks at runtime. - When you add
overrides, leave a comment or an issue link explaining what it works around and what would let you remove it. - Enforce one package manager per repository with a preinstall guard, so a stray
yarn installcannot produce a second source of truth.
Errors You Will Probably Hit Next
npm ERR! peer dep missingnpm ci can only install packages when package.json and package-lock.json are in syncinvalid: react version mismatch in npm ls output
ERESOLVE is npm reporting a real inconsistency, not inventing one. Read the four lines that name the package, the range and the found version, and the fix is usually a single upgrade. Reach for --legacy-peer-deps only when you have checked the constraint is wrong rather than inconvenient - it moves the failure from your terminal to production, and does not remove it.
댓글
댓글 쓰기