Fix npm ERESOLVE Dependency Tree Errors

Quick answer

npm ERESOLVE means npm cannot select versions that satisfy every dependency and peer-dependency constraint. Read the Found, Could not resolve dependency, and peer lines as one conflict. Then align the host and plugin versions, update or replace the incompatible package, regenerate the lockfile deliberately, and verify with npm ci. Do not treat legacy-peer-deps or force as permanent fixes.

The error looks like a package-manager problem:

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree

It is actually npm refusing to make a promise it cannot keep. Your application, its direct dependencies, and their peer dependencies describe a set of version constraints. At least two of those constraints have no compatible version in common.

The popular answer—npm install --legacy-peer-deps—often makes the command return zero. It also tells npm to stop enforcing the peer contract that exposed the problem. That can be an acceptable, short-lived diagnostic choice. It is not the same as resolving the dependency graph.

This guide shows how to read the report, choose a real compatibility strategy, avoid lockfile folklore, and keep the repaired tree stable across local development, Docker, workspaces, CI, and production builds.

Quick Answer

npm ERESOLVE means npm cannot select versions that satisfy every dependency and peer-dependency constraint. Read the Found, Could not resolve dependency, and peer lines as one conflict. Then align the host and plugin versions, update or replace the incompatible package, regenerate the lockfile deliberately, and verify with npm ci. Do not treat legacy-peer-deps or force as permanent fixes.

TL;DR

  • ERESOLVE is a version-constraint conflict, usually a peer dependency mismatch—not a corrupted node_modules directory.
  • Identify the host version npm found, the package that requires a peer, and the peer range it requires.
  • Prefer a compatible plugin release, then a supported host framework version, then a maintained replacement.
  • npm install updates the lockfile after a deliberate manifest change; npm ci verifies the exact committed tree in a clean environment.
  • --legacy-peer-deps ignores peer contracts. --force bypasses safeguards. Neither proves runtime compatibility.
  • Use overrides only for reviewed transitive version policy; it does not rewrite a package's peer API support.
  • A cache clean, deleting the lockfile, or repeatedly reinstalling does not reconcile incompatible semver ranges.

Symptoms

SymptomMeaningNext check
ERESOLVE unable to resolve dependency treenpm could not find one valid install graphRead the first peer conflict
Found: react@19.xnpm selected or was asked for this host versionCheck the root manifest and lockfile
peer react@"^18" from package-x@…A plugin only declares React 18 compatibilityFind a plugin release that supports React 19
Conflicting peer dependency: …npm shows a candidate that satisfies one side, not bothCompare both ranges, not just package names
npm ci fails but npm install worksLockfile and manifest/configuration differCompare the lockfile and install flags
Warning or error only in one workspaceThe root graph has a workspace-specific incompatible edgeRun npm commands from the root with -w

Common Causes

CauseWhy npm rejects itDurable fix
Framework major upgradePlugin peer range stops before the new majorUpgrade or replace the plugin
Old plugin with a narrow peer rangeIt declares support for only one host majorUse a maintained compatible release
Directly installed incompatible peersRoot dependencies request contradictory versionsAlign both direct versions intentionally
A stale lockfile after manifest editspackage.json and package-lock.json describe different treesRun a reviewed npm install and commit the lockfile
Root or workspace mismatchWorkspaces participate in one root resolution graphAlign versions at the root and affected workspace
Private registry metadata differsThe selected manifest or dist-tag may not match public npmInspect the configured registry and package manifest
Unreviewed overridesA pin forces a transitive version outside a package's tested rangeRemove or update the override after verifying impact
Different local and CI npm configFlags change how npm constructs the treeCommit project-level config when an exception is approved

Root Cause: One Graph Must Satisfy Every Range

A normal dependency says “this package needs a copy of another package.” A peer dependency says “this package must run beside the application's copy of a host package that matches this range.” Plugins use peer dependencies for hosts such as React, ESLint, webpack, TypeScript, Angular, and Vue so they do not quietly load a second incompatible host at runtime.

Since npm 7, peer dependencies are installed by default and conflicts can stop resolution. That stricter behavior is useful: two copies of a framework can look installed correctly while breaking context, hooks, plugins, type loading, or shared singleton state at runtime.

How incompatible peer dependency ranges cause npm ERESOLVEThe root application selects React 19.1.0. A modern widget accepts React 19 through its peer range, while a legacy widget requires React 18 only. Because React 19.1.0 cannot satisfy the legacy peer range, npm has no valid dependency tree and raises ERESOLVE.Root applicationstorefront@1.0.0Host selected by rootreact@19.1.0one shared runtimePlugins in the graphmodern + legacy widgetsmodern-widget peer: react ^1919.1.0 satisfies this rangelegacy-widget peer: react ^1819.1.0 does not satisfy this rangeempty intersection
How incompatible peer dependency ranges cause npm ERESOLVE

Read the error as a three-part sentence

Here is an illustrative report:

npm ERR! While resolving: storefront@1.0.0
npm ERR! Found: react@19.1.0
npm ERR! node_modules/react
npm ERR!   react@"19.1.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^18.0.0" from legacy-react-widget@4.2.0
npm ERR! node_modules/legacy-react-widget
npm ERR!   legacy-react-widget@"4.2.0" from the root project

Read it in this order:

  1. The root project wants react@19.1.0.
  2. legacy-react-widget@4.2.0 requires React ^18.0.0 as a peer.
  3. No version can be both exactly 19.1.0 and within the 18.x range.

The error is not saying React is missing. It is saying npm cannot put the widget next to the React version you chose without violating the widget's declared compatibility contract.

Semver details that change the answer

| Range | Accepts | Does not accept | | ---------- | --------------------------------------- | ---------------- | ----------------------- | ------ | | 18.3.1 | only 18.3.1 | 18.3.2 or 19.0.0 | | ^18.3.1 | 18.3.1 up to, but not including, 19.0.0 | 19.0.0 | | ~18.3.1 | 18.3.1 up to, but not including, 18.4.0 | 18.4.0 | | >=18 <20 | 18.x and 19.x | 20.0.0 | | ^18 | | ^19 | compatible 18.x or 19.x | 20.0.0 |

Do not widen a peer range merely to silence npm. If you publish a package, use the broadest range that you have actually tested. If you consume a package, trust a narrow range until you have evidence from its release notes, tests, or maintainer that a later host major is supported.

Step-by-Step Solution

Step 1: Preserve evidence and locate the first real conflict

Do not delete node_modules or package-lock.json first. Capture the report and inspect the project state:

npm --version
node --version
npm config get legacy-peer-deps
npm config get registry
git diff -- package.json package-lock.json

The first Could not resolve dependency block usually contains the primary conflict. Later blocks can be consequences. If npm writes an eresolve-report.txt path, keep it with the build log or pull request while you diagnose the issue; it records the graph npm saw at that point.

Step 2: Inspect the package manifests and installed tree

Ask the registry what the candidate package declares before you change versions:

npm view react-dom@18.2.0 peerDependencies --json
npm view react-dom versions --json
npm view react-dom@latest peerDependencies --json

These commands use a real public package to demonstrate the syntax. For an actual conflict, inspect the exact package and release named in your report. The first command reads a pinned release, the second helps identify a newer release, and the third shows whether the current dist-tag supports your host version. For a private registry, run the same npm view query against the registry that owns the scope; do not compare public npm metadata by accident.

After a previous install has produced a usable tree, these commands explain why a package exists and where duplicates came from:

npm explain react
npm ls react --all

npm explain prints the dependency chain that caused an installed package. npm ls is diagnostic and can exit nonzero when the tree is invalid; that nonzero status is useful evidence, not a reason to ignore its output.

Step 3: Choose one compatible version line

Decision tree for resolving npm ERESOLVE without ignoring peer dependenciesRead the first peer conflict. First look for a plugin release that supports the chosen host major. If none exists, decide whether the host version can be aligned temporarily. Otherwise replace or remove the incompatible package. Regenerate the lockfile with npm install and verify it with npm ci. Legacy peer flags are only a documented temporary exception.npm ERR! ERESOLVERead Found version + peer rangeidentify host and pluginIs there a plugin release thatsupports the chosen host major?yesUpgrade pluginreview peer range + testsnoCan the host versionbe aligned temporarily?yesUse supported host lineplan the larger migrationnoReplace or remove pluginor wait for upstream supportnpm install → review locknpm ci → test + buildthen verify
Decision tree for resolving npm ERESOLVE without ignoring peer dependencies

Use this order of preference:

  1. Upgrade the plugin or adapter. Find a release whose peer range includes your chosen host major. This is the usual fix after a React, Angular, TypeScript, ESLint, or webpack upgrade.
  2. Align the host version. If the plugin is required and a compatible release does not exist, use the supported host major while you plan the broader upgrade.
  3. Replace or remove the incompatible package. This is safer than freezing a central framework forever for an abandoned plugin.
  4. Contribute or wait for an upstream compatibility release. A peer range update should be accompanied by real tests against the new host version.

For the illustrative conflict, an aligned manifest might look like this:

{
  "name": "storefront",
  "private": true,
  "dependencies": {
    "react": "19.1.0",
    "react-dom": "19.1.0",
    "modern-react-widget": "6.0.0"
  }
}

The important property is not the package name. It is that the widget's published peerDependencies.react range includes React 19. Do not install React 18 beside React 19 just to make npm finish; applications that rely on a single framework runtime are especially vulnerable to duplicate copies.

Step 4: Make the manifest change, then regenerate the lockfile

Edit package.json intentionally, then let npm resolve a new lockfile:

npm install
git diff -- package.json package-lock.json

Review both files. The lockfile should change because the dependency policy changed. A giant unrelated lockfile rewrite usually means the command, npm version, registry, or existing manifest state changed too; investigate before committing it.

If you need to prove that the updated lockfile works from a clean dependency directory, use the command designed for that purpose:

rm -rf node_modules
npm ci

On Windows PowerShell:

Remove-Item -Recurse -Force node_modules
npm ci

npm ci requires package-lock.json to agree with package.json and does not rewrite either file. It is a verification and deployment command, not the tool for choosing new versions.

macOS, Windows, Linux, WSL, CPU, and GPU

ERESOLVE is driven by package metadata and semver ranges, so its root cause is platform-independent. Use the macOS/Linux rm -rf node_modules command or the PowerShell alternative shown above only after the manifest and lockfile are valid. Do not share a node_modules directory between Windows and WSL; run npm ci in the environment that will execute the application.

CPU architecture, GPU availability, CUDA, and native module binaries can cause later install or runtime failures, but they do not make incompatible peer ranges compatible. In a container or cloud build, solve ERESOLVE first, then address any platform-specific native dependency error separately.

When Flags and Overrides Are Appropriate

--legacy-peer-deps: a temporary exception, not a resolution

This command asks npm to ignore peer dependencies while constructing the tree:

npm install --legacy-peer-deps

It is useful for a narrow purpose: confirming that a package is the only resolution blocker, or unblocking a short-lived branch when you have separately validated runtime compatibility. It is not proof that the peer mismatch is safe.

If a team deliberately accepts this temporary risk, make the setting explicit and keep local and CI behavior identical:

# .npmrc
legacy-peer-deps=true

Then regenerate the lockfile with the same policy and use the same policy in CI:

npm install
npm ci

Remove the setting once the package versions are aligned. npm documents that legacy-peer-deps ignores peer dependencies, and recommends against it because it no longer enforces contracts that meta-dependencies may rely on. Do not put this setting in a global user .npmrc: it would silently affect unrelated projects.

Why --force is usually worse

npm install --force tells npm to relax protections. It can turn a clear install-time incompatibility into a later test or production failure. It is not a substitute for checking package release notes, peer ranges, or runtime tests.

Avoid both commands when the conflict concerns:

  • a framework singleton such as React, Angular, Vue, or a state-management host;
  • build tooling that shares a compiler, loader, or plugin API;
  • authentication, payment, cryptography, or security middleware;
  • a production dependency that has not been tested with the forced tree.

overrides: enforce a transitive version, not peer compatibility

Root-level overrides are valuable when you need one reviewed version of a transitive dependency, such as a patched parser or HTTP client. They do not change the API compatibility claimed by a package's peerDependencies.

{
  "name": "storefront",
  "private": true,
  "dependencies": {
    "@company/sdk": "4.2.0"
  },
  "overrides": {
    "@company/sdk": {
      "undici": "6.21.1"
    }
  }
}

This example asks npm to use undici@6.21.1 below @company/sdk. Review the SDK's changelog and tests before committing the policy. An override can reduce duplicate transitive versions or apply a vetted security fix; it cannot make a React-18-only plugin actually support React 19.

For a direct dependency, npm rejects an override that specifies a different version from the direct dependency itself. The npm documentation supports the $name reference when the intended override must match a root dependency. That restriction prevents a manifest from expressing contradictory top-level policy.

npm dedupe: cleanup after compatibility, not before it

npm dedupe can hoist compatible duplicate dependencies without adding new modules. It is useful after an aligned install when you want a smaller tree:

npm dedupe
npm ls react --all

It cannot create an intersection between ^18 and ^19. Run it only after the peer conflict is solved, and review the resulting lockfile as you would any dependency change.

Workspaces, Docker, CI/CD, and Private Registries

npm workspaces

Workspaces share a root install and lockfile. A package inside one workspace can therefore constrain the same host package used by another workspace. Start from the repository root and scope the command only after you understand the root graph:

npm install -w @company/web
npm explain react -w @company/web
npm ls react --all --workspaces

The -w option selects a workspace, but it does not make an incompatible monorepo peer relationship disappear. Put common framework versions in one reviewed place, such as a root policy or shared package manifest, rather than letting every workspace choose unrelated majors.

Docker and Linux containers

Docker is a clean-room test for the lockfile, not a special npm solver. Copy the manifests first, install deterministically, then copy application source:

FROM node:22-alpine

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build

CMD ["npm", "run", "start"]

Do not copy the host node_modules directory into a Linux container. Native modules, optional dependencies, CPU architecture, and operating system differences can make a host installation invalid even when dependency versions match. If a committed, non-secret project .npmrc defines a temporary legacy-peer-deps policy, copy it before RUN npm ci. Do not bake registry auth tokens into an image; inject them through the build platform's secret mechanism instead.

CI/CD and cloud builds

Record the Node and npm versions in CI logs, use npm ci, and fail on an unexpected lockfile change:

node --version
npm --version
npm ci
npm test
npm run build

If a lockfile was created using options that affect the tree, such as legacy-peer-deps or install-links, npm requires compatible settings for npm ci. A local install that uses one set of flags and a cloud build that uses another is not reproducible.

Private registries and scoped packages

An ERESOLVE report can be correct even if public npm shows a different peer range. Your scope may resolve from a private registry, proxy, or cached package metadata. Confirm the source before selecting a version:

npm config get registry
npm config list --location=project

The output identifies the registry and project-level configuration that the install uses. Then run npm view for the exact scoped package and registry from that configuration. Never paste registry auth tokens, .npmrc files, or complete npm debug logs into public issue trackers. Those logs can include private package names, registry URLs, and credentials.

Incorrect Fixes and Why They Fail

Incorrect approachWhy it is weakBetter action
Delete package-lock.json immediatelyRemoves a reproducibility record but not incompatible rangesDiagnose, align versions, then regenerate intentionally
Delete node_modules repeatedlyReinstalls the same impossible graphChange the conflicting manifest constraints
npm cache clean --forceCache contents do not alter peer rangesUse only for a proven cache problem
Add the peer package manuallyIt may strengthen the conflict or install another incompatible versionMatch the plugin's declared peer range
Move a package to devDependenciesnpm still resolves peer relationships during installUse a compatible release or remove it
Commit global legacy-peer-depsHides conflicts in every project on a machineUse a reviewed project-level temporary exception
Add an override for a peer mismatchPins a dependency but does not make APIs compatibleUpgrade, downgrade, replace, or test upstream support
Blindly use --force in CITurns early, actionable failure into runtime riskMake the graph and lockfile deterministic

Verification Steps

An npm install that exits successfully is not enough. Verify the repaired dependency graph at the boundaries where it will run:

  1. Inspect the selected host version and dependency path.

    npm ls react --all
    npm explain react

    Substitute the actual host package from your ERESOLVE report. Confirm that the expected version is present and that no unexpected duplicate copies were introduced.

  2. Test the clean lockfile.

    rm -rf node_modules
    npm ci

    npm ci must finish without modifying package-lock.json.

  3. Run the checks that exercise the shared host.

    npm test
    npm run build

    For a framework plugin, run at least the integration path that loads the plugin. An installable tree is not evidence that a peer API is compatible.

  4. Repeat the same command in CI or Docker. Verify the Node version, npm version, registry, and project .npmrc are the intended ones.

  5. Review the dependency diff. Confirm that package additions, removals, overrides, and lockfile changes are all explained by the chosen fix.

Prevention

Use these practices to avoid recurring ERESOLVE incidents:

  • Upgrade a framework and its official adapters as one planned change, not as scattered individual package updates.
  • Read peer dependencies before adding plugins to a major-version migration.
  • Keep direct host packages aligned: React with React DOM, Angular packages, ESLint with its plugins, and compatible TypeScript tooling.
  • Commit exactly one package-lock.json for an application and use npm ci in CI, Docker, and release builds.
  • Pin or document Node and npm versions so local resolution matches CI.
  • Keep project-specific npm configuration in a reviewed repository .npmrc, never only in a developer's global settings.
  • Review every overrides entry with an owner, reason, test evidence, and removal condition.
  • Run npm outdated on a schedule so package compatibility work is incremental rather than a multi-major emergency.
  • Run npm audit after the graph resolves, but do not let an automated audit upgrade bypass your peer compatibility review.

For related project debugging, see:

Official References

FAQs

How do I fix npm ERR! ERESOLVE unable to resolve dependency tree?

Read the Found version and the peer range in the first conflict block. Then install a plugin version that supports the chosen host, align the host version to the plugin's supported major, or replace the plugin. Run npm install to update the lockfile and npm ci from a clean directory to verify the committed tree.

Is npm install --legacy-peer-deps a safe permanent fix?

No. It makes npm ignore peer dependencies during resolution, so it stops npm from enforcing a contract that may be required at runtime. Use it only as a documented, temporary exception after testing, and remove it when compatible versions are available. Do not add it globally for every npm project.

Why does npm ci fail when npm install succeeds?

npm install is allowed to resolve and update package-lock.json. npm ci requires the lockfile to agree with package.json and installs that exact tree without rewriting it. It also needs compatible npm configuration. Recreate the lockfile deliberately with the project settings, commit it, and run the same settings in CI.

Can npm overrides solve a peer dependency conflict?

Usually not. Overrides decide which version of an existing dependency edge npm installs. They do not change the peer range declared by a plugin or guarantee that a plugin works with a new framework API. Use overrides for a reviewed transitive pin, not to claim unsupported framework compatibility.

Does removing node_modules or clearing the npm cache fix ERESOLVE?

Not when semver ranges conflict. A fresh install reads the same package.json and package manifests, so it reaches the same contradiction. Removing node_modules is useful after you have changed and committed a valid lockfile; cache cleaning is only relevant when you have evidence of a cache issue.

Why do React peer dependency conflicts matter so much?

React packages often need to share one compatible runtime. Installing mismatched or duplicate copies can result in invalid hook calls, lost context, renderer mismatches, or libraries that appear installed but fail when rendered. Treat a React peer conflict as a compatibility decision, not merely an npm warning.

Key takeaways

  • ERESOLVE is a version-constraint conflict, usually a peer dependency mismatch—not a corrupted node_modules folder.
  • The npm report identifies the installed or requested version, the package that requires a range, and the incompatible range.
  • Update the plugin, align the host framework version, or replace the unsupported package before reaching for install flags.
  • legacy-peer-deps ignores peer contracts; use it only as a documented, temporary compatibility exception.
  • npm overrides can pin a transitive dependency but cannot make an incompatible peer API work.
  • Commit one reviewed package-lock.json and make local, Docker, and CI installs use compatible npm configuration.

Frequently asked questions

How do I fix npm ERR! ERESOLVE unable to resolve dependency tree?

Read the ERESOLVE report to find the host package npm found and the peer range required by the conflicting package. Install a version of the plugin that supports the host, change the host to a supported version, or replace the incompatible plugin. Then run npm install to update package-lock.json and verify a clean checkout with npm ci. Avoid using legacy-peer-deps as the permanent fix.

What causes npm ERESOLVE?

ERESOLVE occurs when npm cannot build one dependency graph that satisfies every declared version range. The most common cause is an incompatible peer dependency: for example, an application requests React 19 while a plugin declares peerDependencies of React 18 only. It can also result from incompatible workspaces, a stale lockfile after manifest edits, private-registry metadata, or a conflicting override.

Is npm install --legacy-peer-deps safe?

It can unblock a local investigation, but it is not a generally safe fix. npm documents that legacy-peer-deps ignores peerDependencies while constructing the tree, so npm no longer verifies a compatibility contract that the packages may rely on. If a team accepts it temporarily, record the reason and use the same setting in npm install and npm ci until the dependency conflict is fixed.

Should I use npm install --force for ERESOLVE?

Usually no. Force tells npm to continue past protections, but it does not prove that mismatched packages work at runtime. It can create a tree that installs but fails later through duplicate framework singletons, missing APIs, invalid hooks, or plugin crashes. Prefer an aligned upgrade or downgrade, a maintained replacement, or an upstream fix.

Does deleting package-lock.json fix ERESOLVE?

Not by itself. ERESOLVE is usually caused by constraints in package.json and dependency manifests, which deleting a lockfile does not change. Removing the lock can also make npm choose newer transitive versions and hide the original evidence. Change the intended version constraints first, then regenerate and review the lockfile deliberately.

What is the difference between npm install and npm ci after an ERESOLVE fix?

npm install resolves the manifest and updates package-lock.json when needed. npm ci requires a lockfile that agrees with package.json, removes existing node_modules, and installs the locked tree without modifying the lockfile. Use npm install while deliberately repairing the graph; commit the reviewed lockfile; then use npm ci in CI, Docker, and clean verification.


Related Posts