Why This Keeps Happening (Spoiler: npm Sucks at Peer Dependencies)

npm's dependency resolver was designed by someone who clearly never had to debug this shit at 3am. Install one UI library that worked fine yesterday, suddenly your entire project won't build because "peer dependency conflicts."

npm's algorithm tries to flatten dependency trees but fails spectacularly when peer deps conflict.

Real Example from 2025:

npm ERR! Could not resolve dependency:
npm ERR! peer react@"^17.0.0" from some-ui-library@1.2.3
npm ERR! node_modules/some-ui-library
npm ERR! some-ui-library@"^1.2.3" from the root project
npm ERR! peer react@"^18.0.0" from another-component@2.1.0

This error message is telling you that some-ui-library expects React 17, but another-component expects React 18, and npm can't reconcile this conflict.

Peer Dependencies - The Root of All Evil

Peer dependencies are npm's way of saying "you figure it out." Package maintainers specify what version of React they think they need, but don't actually install it. Your project has to provide the right version.

The official npm docs explain this poorly, but this dev.to guide breaks down why this design is fundamentally flawed.

Would be fine if library maintainers updated their shit when new versions drop. But they don't. React 18 came out in March 2022 and I'm STILL finding packages that declare "react": "^17.0.0" as a peer dep. It's 2025. Get your fucking act together.

Check npm trends and you'll see how many packages are still stuck on ancient peer deps. The React ecosystem is full of libraries that haven't been updated in years.

Version Range Clusterfuck

You know how lodash v3 and lodash v4 are completely different? Well npm doesn't. One package wants "lodash": "^4.0.0" and another wants "lodash": "^3.10.1" and npm just gives up.

The semver spec was supposed to prevent this, but in practice it's broken. Check bundlephobia to see how many different versions are floating around.

Semver ranges were supposed to solve this but actually make it worse. Updated our TypeScript config yesterday and suddenly three dev tools stopped working because they all had different opinions about what version they needed.

Transitive Dependency Hell

The absolute worst: conflicts from packages you never even installed. You add some chart library, it depends on some utility lib, which depends on React 17. Your project uses React 18. Now your build is broken because of some package buried three levels deep that you've never heard of.

Why 2025 Made Everything Worse

React 19 dropped in December 2024 and broke half the ecosystem. Every UI library I actually use is still on React 17/18 peer deps. Want to try React's new concurrent features? Good luck - MUI hasn't updated, Chakra's dragging their feet, and don't even get me started on all the random component libraries.

TypeScript 5.x fucked everything too. They changed how types get resolved and suddenly ESLint plugins, testing libraries, build tools - everything just stopped working. "Minor" version updates my ass.

TypeScript breaking changes happen way more often than they admit.

Oh and ESM vs CommonJS is still a disaster. Import some package expecting it to work, get ERR_REQUIRE_ESM and realize you just wasted 20 minutes because the docs didn't mention it's ESM-only.

The Node.js documentation explains dual packages but most library authors don't give a shit about backwards compatibility.

Reading the Error Messages

npm's error messages are written by people who hate developers, but they follow patterns once you learn to decode the bullshit:

npm ERR! ERESOLVE could not resolve
npm ERR! 
npm ERR! While resolving: my-project@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 problematic-package@1.2.3

This is telling you:

  1. "While resolving: my-project@1.0.0" - npm is trying to install dependencies for your root project
  2. "Found: react@18.2.0" - Your project has React 18 installed
  3. "Could not resolve dependency: peer react@"^17.0.0"" - But problematic-package expects React 17

So you've got three options: downgrade React (fuck that), upgrade the package (doesn't exist), or force npm to use what you want with overrides.

Real Cost of This Bullshit

MUI v5 migration took us 3 weeks. Not because the code was hard - because dependency resolution kept breaking in weird ways. Business kept asking why the feature was late while I'm explaining that our button library has opinions about React versions.

Lost a whole weekend once because some security audit tool flagged lodash vulnerabilities, but updating lodash broke our build tools, so we were stuck with old vulnerable versions until someone updated their peer deps.

And don't get me started on "works on my machine" - when everyone has slightly different node_modules because they installed packages at different times, nothing fucking works consistently.

How to Fix This Shit When It Breaks

Just Nuke Everything First

Look, 80% of the time this works and it's 3pm with a deploy in 2 hours:

## Step 1: Burn it all down
rm -rf node_modules package-lock.json
npm cache clean --force

## Step 2: Try again
npm install

## Step 3: If npm is still being a dick, use the legacy resolver
npm install --legacy-peer-deps

--legacy-peer-deps tells npm to use the old v6 resolver that wasn't as anal about peer dependencies. The official docs call it a "compatibility mechanism" but really it's just "make npm stop being so fucking strict."

Read the npm v7 breaking changes to understand why this flag exists.

Works most of the time, especially when migrating npm versions or dealing with shitty unmaintained libraries. Fails when packages are actually incompatible or when you've got deep transitive dependency fuckery.

npm Overrides When You Need Surgery

If nuking doesn't work, force specific versions with overrides. Way better than the legacy flag because you control exactly what gets overridden:

{
  "overrides": {
    "react": "^18.2.0",
    "some-problematic-package": {
      "react": "^18.2.0"
    }
  }
}

This tells npm: "I don't care what some-problematic-package says it needs - force it to use React 18.2.0."

The npm overrides documentation explains the syntax, and this Stack Overflow thread has real examples that actually work.

Advanced override patterns:

{
  "overrides": {
    // Force ALL packages to use React 18
    "react": "18.2.0",
    
    // Override nested dependencies
    "package-a": {
      "package-b": {
        "lodash": "4.17.21"
      }
    },
    
    // Use wildcards for similar packages
    "@types/*": {
      "typescript": "5.1.6"
    }
  }
}

War Story: MUI v5 completely broke our build. Half our components went dark because they changed all their import paths. Design team was like "yeah we'll fix that eventually" while I'm staring at CI failures and a customer demo tomorrow.

Had to jury-rig it with overrides:

{
  "overrides": {
    // Started with just this
    "react": "18.2.0"
    
    // Then shit started breaking and had to add more...
    // "@material-ui/core": {
    //   "react": "18.2.0"  // this one was being especially difficult 
    // },
    
    // Some chart library wouldn't cooperate 
    // "legacy-chart-thing": {
    //   "react": "18.2.0"
    // }
    
    // Storybook just gave up entirely
    // "@storybook/addon-essentials": {
    //   "react": "18.2.0"  // still not sure why this fixed it
    // }
  }
}

Bought us 2 weeks to do the actual migration. Ugly but it worked.

If You Use Yarn

Yarn calls it `resolutions` instead of overrides but same idea:

Check this comparison between npm overrides and Yarn resolutions.

{
  "resolutions": {
    "react": "18.2.0",
    "**/lodash": "4.17.21"
  }
}

Yarn's algorithm handles complex trees better than npm usually. That's why half the React teams I know switched around the npm v7 disasters.

Read this analysis of why different package managers resolve dependencies differently.

When One Package Is the Problem

Usually it's one shitty package ruining everything. Figure out which one:

## See what's pulling in conflicting versions
npm ls typescript
npm ls react

## Check what the problem package actually wants
npm info some-problematic-package peerDependencies

For TypeScript conflicts, just pin the version everyone else can live with:

npm install typescript@5.1.6 --save-dev --save-exact

ESLint plugins are especially anal about TypeScript versions:

npm install @typescript-eslint/eslint-plugin@latest --force

Update Packages (But Carefully)

Sometimes conflicts happen because packages are old as shit and using ancient peer deps. npm-check-updates shows what's outdated:

npx npm-check-updates
## Then update stuff one at a time, not all at once
ncu -i

Don't do ncu -u && npm install unless you like pain. Update dev dependencies first, then minor versions, then major versions if you absolutely have to.

Check npm-check-updates best practices and this guide on safe dependency updates.

Fork Abandoned Packages (Last Resort)

When the package maintainer disappeared and you're stuck with React 16 peer deps:

## Fork the repo on GitHub
## Clone your fork - example with a real abandoned React library
git clone https://github.com/reactjs/react-transition-group.git
cd abandoned-package

## Update package.json peer dependencies
## Fix to support React 18
"peerDependencies": {
  "react": "^17.0.0 || ^18.0.0"
}

## Publish to npm with scoped name
npm publish --access public

Then in your project:

{
  "dependencies": {
    "@yourname/fixed-package": "^1.0.0"
  }
}

react-router v6 migration was a disaster but community forks saved us. Storybook v7 broke all the addons so people just forked them and fixed the peer deps. Sometimes community forks work better than official packages.

Find community solutions on GitHub Issues and Stack Overflow npm tags.

CI Breaks When Local Works (Of Course)

Builds work locally but CI shits itself because npm resolves dependencies differently. Classic.

Docker Builds:

## Use exact npm version
FROM node:18-alpine
RUN npm install -g npm@9.8.1

## Copy lock files first for better caching
COPY package*.json ./
RUN npm ci --only=production

## Override for problematic packages
ENV NPM_CONFIG_LEGACY_PEER_DEPS=true
RUN npm ci --only=production

GitHub Actions:

- name: Setup Node.js
  uses: actions/setup-node@v3
  with:
    node-version: '18'
    cache: 'npm'

- name: Install with legacy peer deps
  run: npm ci --legacy-peer-deps

Vercel/Netlify Builds:

{
  "scripts": {
    "build": "npm install --legacy-peer-deps && next build"
  }
}

Debug Commands That Actually Help

npm ls    # See what's installed
npm why react    # Figure out what's pulling in React 17 when you want 18

Time Reality Check

  • Nuking node_modules: 5 minutes or 2 hours when npm cache is corrupted
  • npm overrides: 15 minutes to find the conflict, hour to fix what you broke
  • Package updates: 30 minutes that turns into a dependency rabbit hole
  • Fork-and-fix: Half a day, then you own the problem forever

Overrides are the sweet spot. Faster than forking, more reliable than --force.

When Nothing Works

Sometimes packages are just incompatible. React 16 vs React 18, Vue components in React projects, packages that haven't been updated since 2019 - some conflicts can't be fixed.

Try switching to Yarn or pnpm - different resolution algorithms might solve what npm can't. Or find a different package that's actually maintained.

If all else fails, remove the package. Better to lose a feature than have a broken build.

Prevention (AKA Don't Be an Idiot)

Can't prevent all conflicts but you can avoid the dumb ones.

Commit Your Fucking Lock Files

Always commit `package-lock.json`. I lost a weekend once because someone gitignored it and everyone had different dependency trees. "Works on my machine" but CI fails every time.

Read this guide on why lock files matter and npm's official guidance on lock file management.

## In your .gitignore, NEVER ignore lock files
## ❌ Wrong
package-lock.json

## ✅ Correct - commit lock files
node_modules/
.env

Pin Your Versions (Semver Is a Lie)

Use exact versions for anything important:

{
  "dependencies": {
    "react": "18.2.0",    // not "^18.2.0"
    "typescript": "5.1.6"
  }
}

Semver says minor updates should be safe. That's bullshit. Had a "minor" React component library update completely change the API and break our entire app. Pin it and update manually when you have time to test.

Learn about version pinning strategies and why semver is broken in practice.

CI That Catches Conflicts

Add this to your GitHub Actions to catch lock file fuckups:

- name: Check dependencies
  run: npm ci --dry-run
  
- name: Audit security
  run: npm audit --audit-level high

Use .nvmrc So Everyone Doesn't Fuck Up Node Versions

echo "18.17.0" > .nvmrc
## Then everyone runs: nvm use

Different Node versions resolve dependencies differently. Use the same version or deal with random CI failures.

Node.js release schedule shows which versions are LTS. nvm documentation explains how to manage versions properly.

Before Adding Any Package

Check if it'll break your build:

npm info package-name peerDependencies
npx bundlephobia package-name    # Check if it's 2MB like that date library some junior dev tried to add

Also check npm trends so you don't add a package that's been abandoned for 2 years.

Use npmjs.com to check last publish date and GitHub to see if the repo is still maintained.

Renovate Bot (If You Want Automated PRs)

Renovate automates dependency updates with intelligent scheduling and risk assessment.

{
  "extends": ["config:base"],
  "rangeStrategy": "pin",
  "packageRules": [
    { "matchUpdateTypes": ["major"], "enabled": false }
  ]
}

Disables major updates because those always break shit. Minor updates only.

Micro-frontends (If You're Into That)

Read Martin Fowler's micro-frontends guide and this practical implementation.

Each team manages their own dependencies so conflicts don't cascade across everything. Good for big apps with multiple teams stepping on each other.

"peerDependencies": { "react": ">=17.0.0 <19.0.0" }

Framework Upgrades (The Painful Reality)

Don't upgrade React, Next.js, or any major framework until you absolutely have to. Create an experimental branch, update dev tools first, then spend weeks fixing what breaks.

React's upgrade guide and Next.js migration docs help, but expect pain.

git checkout -b react-19-experiment
npm install react@19.0.0-rc.0 --save-exact
## Then discover half your UI library doesn't work

Use Built-in APIs When Possible

// Don't add axios if you can use fetch
const response = await fetch(url);

Fewer dependencies = fewer conflicts.

The Node.js built-in modules cover most common needs. Check You Don't Need Lodash for native alternatives.

Time Investment Reality

Setting this up takes 3 hours. Weekly maintenance is 30 minutes unless something breaks, then it's 2 hours debugging why a security patch nuked the build.

Every few months you'll spend a day fixing whatever the ecosystem broke. Yearly framework upgrades are a full week of pain.

But teams that do this have way fewer 3am dependency disasters.

Read dependency management best practices and this comprehensive security guide.

Common Questions

Q

Should I use --force or --legacy-peer-deps?

A

Use `--legacy-peer-deps`. Don't use `--force` unless you want React 16 and React 18 installed at the same time and 3 hours of debugging cryptic errors. I learned this the hard way.

See this detailed comparison of both approaches.

Q

Why does npm ls show different versions than package.json?

A

npm tries to find versions that work for ALL your dependencies. Your package.json might say React 18, but if some other package needs React 17, npm might compromise and install 17. Run `npm ls react` to see what's driving the decision.

The npm dependency resolution docs explain why this happens.

Q

Can I delete package-lock.json?

A

Yeah, but you'll get different versions of everything when npm recalculates the tree. Sometimes this fixes impossible conflicts, sometimes it breaks working code. Delete it, run npm install, commit the new lock file, and test everything.

Q

How do I check if a package will break my build before installing?

A

`npm info package-name peerDependencies` shows what it expects. I always check this after installing a UI library that wanted React 16 while we were on React 18.

Also useful: `npm explain package-name` shows why a package was installed.

Q

What's the difference between npm overrides and Yarn resolutions?

A

Same thing, different syntax. npm uses "overrides", Yarn uses "resolutions". Both force specific versions.

Q

Why do React 18 projects have so many conflicts?

A

React 18 changed a bunch of stuff and library maintainers are slow to update their peer deps. Three years later and half the ecosystem still declares React 17 compatibility only. It's ridiculous how long it takes to update one line in package.json.

Check React DevTools to see which components are causing conflicts.

Q

Can I use multiple versions of the same package?

A

Not with standard npm. Use workspaces if you absolutely need different versions in different parts of your monorepo.

Alternatively, Lerna or Rush handle multi-package repos better.

Q

My CI fails but local works fine. Why?

A

Different Node versions, missing .nvmrc, or CI using `npm install` instead of `npm ci`. Use the same Node version and exact same commands.

Read this guide on Node.js CI best practices.

Q

Should I switch to Yarn or pnpm?

A

Maybe. Delete node_modules and package-lock.json, run `yarn install` or `pnpm install`. Different resolution algorithms might solve what npm can't.

Compare package manager performance to see which works best for your project.

Q

Can I prevent all dependency conflicts?

A

No. The JS ecosystem is chaos. You can minimize them with exact versions and good hygiene, but some asshole will always publish a package that breaks your build.

Follow security best practices and this dependency management guide.

Resources That Actually Help

Related Tools & Recommendations

tool
Similar content

npm - The Package Manager Everyone Uses But Nobody Really Likes

It's slow, it breaks randomly, but it comes with Node.js so here we are

npm
/tool/npm/overview
100%
troubleshoot
Similar content

npm ELIFECYCLE Error: Debug, Fix & Prevent Common Issues

When npm decides to shit the bed and your deploy is fucked at 2am

npm
/troubleshoot/npm-err-code-elifecycle/common-fixes-guide
87%
tool
Similar content

Webpack: The Build Tool You'll Love to Hate & Still Use in 2025

Explore Webpack, the JavaScript build tool. Understand its powerful features, module system, and why it remains a core part of modern web development workflows.

Webpack
/tool/webpack/overview
84%
troubleshoot
Similar content

Solve npm EACCES Permission Errors with NVM & Debugging

Learn how to fix frustrating npm EACCES permission errors. Discover why npm's permissions are broken, the best solution using NVM, and advanced debugging techni

npm
/troubleshoot/npm-eacces-permission-denied/eacces-permission-errors-solutions
82%
review
Recommended

Vite vs Webpack vs Turbopack: Which One Doesn't Suck?

I tested all three on 6 different projects so you don't have to suffer through webpack config hell

Vite
/review/vite-webpack-turbopack/performance-benchmark-review
69%
troubleshoot
Similar content

Fix npm EACCES Permission Errors in Node.js 22 & Beyond

EACCES permission denied errors that make you want to throw your laptop out the window

npm
/troubleshoot/npm-eacces-permission-denied/latest-permission-fixes-2025
65%
integration
Recommended

Jenkins + Docker + Kubernetes: How to Deploy Without Breaking Production (Usually)

The Real Guide to CI/CD That Actually Works

Jenkins
/integration/jenkins-docker-kubernetes/enterprise-ci-cd-pipeline
60%
review
Recommended

Which JavaScript Runtime Won't Make You Hate Your Life

Two years of runtime fuckery later, here's the truth nobody tells you

Bun
/review/bun-nodejs-deno-comparison/production-readiness-assessment
60%
tool
Similar content

Node.js Security Hardening Guide: Protect Your Apps

Master Node.js security hardening. Learn to manage npm dependencies, fix vulnerabilities, implement secure authentication, HTTPS, and input validation.

Node.js
/tool/node.js/security-hardening
60%
tool
Similar content

npm Enterprise Troubleshooting: Fix Corporate IT & Dev Problems

Production failures, proxy hell, and the CI/CD problems that actually cost money

npm
/tool/npm/enterprise-troubleshooting
58%
howto
Recommended

Install Node.js with NVM on Mac M1/M2/M3 - Because Life's Too Short for Version Hell

My M1 Mac setup broke at 2am before a deployment. Here's how I fixed it so you don't have to suffer.

Node Version Manager (NVM)
/howto/install-nodejs-nvm-mac-m1/complete-installation-guide
57%
troubleshoot
Similar content

Fix MongoDB "Topology Was Destroyed" Connection Pool Errors

Production-tested solutions for MongoDB topology errors that break Node.js apps and kill database connections

MongoDB
/troubleshoot/mongodb-topology-closed/connection-pool-exhaustion-solutions
50%
troubleshoot
Similar content

Fix Slow Next.js Build Times: Boost Performance & Productivity

When your 20-minute builds used to take 3 minutes and you're about to lose your mind

Next.js
/troubleshoot/nextjs-slow-build-times/build-performance-optimization
43%
alternatives
Recommended

Your Monorepo Builds Take 20 Minutes Because Yarn Workspaces Is Broken

Tools that won't make you want to quit programming

Yarn Workspaces
/alternatives/yarn-workspaces/modern-monorepo-alternatives
43%
tool
Recommended

Webpack Performance Optimization - Fix Slow Builds and Giant Bundles

integrates with Webpack

Webpack
/tool/webpack/performance-optimization
40%
tool
Recommended

GitHub Actions Security Hardening - Prevent Supply Chain Attacks

integrates with GitHub Actions

GitHub Actions
/tool/github-actions/security-hardening
40%
alternatives
Recommended

Tired of GitHub Actions Eating Your Budget? Here's Where Teams Are Actually Going

integrates with GitHub Actions

GitHub Actions
/alternatives/github-actions/migration-ready-alternatives
40%
tool
Recommended

GitHub Actions - CI/CD That Actually Lives Inside GitHub

integrates with GitHub Actions

GitHub Actions
/tool/github-actions/overview
40%
howto
Recommended

Set Up Bun Development Environment - Actually Fast JavaScript Tooling

competes with Bun

Bun
/howto/setup-bun-development-environment/overview
39%
troubleshoot
Recommended

Fix Docker "Permission Denied" Error on Ubuntu

That fucking "Got permission denied while trying to connect to the Docker daemon socket" error again? Here's how to actually fix it.

Docker Engine
/troubleshoot/docker-permission-denied-ubuntu/permission-denied-fixes
39%

Recommendations combine user behavior, content similarity, research intelligence, and SEO optimization