npm install跑到一半停住,最后几行长这样:
npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: myapp@1.0.0 npm ERR! Found: react@17.0.1 npm ERR! node_modules/react npm ERR! react@"17.0.1" from the root project npm ERR! npm ERR! Could not resolve dependency: npm ERR! peer react@"^16.3.0" from react-bootstrap-table-next@4.0.3 npm ERR! npm ERR! Fix the upstream dependency conflict, or retry npm ERR! this command with --force or --legacy-peer-deps npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
这不是网络问题,删缓存也没用。npm把整棵依赖树算完之后,发现有两处对同一个包要求了互不兼容的版本,它不肯替你猜该留哪一个,于是停下来报错。同样的package.json在npm 6上只是冒一句警告,装完照样能跑。
1、把报错里那几行读明白
ERESOLVE的输出是有结构的:While resolving后面是当前项目,Found后面是已经定下来的版本,Could not resolve dependency后面是谁在要求另一个版本。上面这段翻译成人话就是项目自己装了react 17,而react-bootstrap-table-next 4.0.3声明的peer是react 16,两边对不上。
完整的冲突报告npm已经写在磁盘上了,路径就在报错最后几行里,Windows是C:\Users\用户名\AppData\Local\npm-cache\eresolve-report.txt,Linux和macOS在 ~/.npm目录下那份 _logs旁边。报告里按顺序列出了每一次冲突,比终端里被截断的那段好读得多。
2、先确认npm和Node的版本
npm -v node -v
npm 7.0、8.0、9.0、10.0,从这一代开始自动安装peerDependencies并把冲突当成错误,所以老项目换个机器就装不上的情况特别多。Node 14.0、16.0、18.0、20.0,这几个大版本分别带的是npm 6、8、9、10,本地开发机升到Node 20而CI镜像还停在Node 14,同一份package.json一边报错一边正常,差的就是这儿。
3、救急用 --legacy-peer-deps
npm install --legacy-peer-deps
这个参数让npm退回6.x的解析逻辑,忽略全部peer冲突直接装完。赶时间的时候很好使,但长期留在项目里不合适:它盖住的是真实的不兼容,构建可能过,跑到某个组件才炸,那时候离装包这一步已经很远了。
真要固定下来就写进 .npmrc:
legacy-peer-deps=true
有一点容易踩:npm ci也读这份配置。如果package-lock.json是开着这个开关生成的,别人拉下来跑npm ci不带同样配置就会重新报ERESOLVE,CI上尤其常见。
4、正解是让两边的版本对上
动手前先看对方到底要求什么:
npm view react-bootstrap-table-next@4.0.3 peerDependencies npm view react-bootstrap-table-next versions --json
第一条打出它声明的peer范围,第二条列出所有可用版本。遇到过的情况是这个包在4.0.3只认react 16,升到4.1.0之后范围放宽到17,那就升包;要是作者一直没更新,就把项目的react降到它能接受的范围。这步慢一点,但是唯一不留后患的走法。
5、只覆盖一个嵌套依赖:overrides
overrides是npm 8.3.0、9.0.0、10.5.0,这一代才有的字段,2021年12月随npm 8.3引入,写在使用方的package.json顶层,能强制指定某个包在任何位置都用同一个版本:
{
"dependencies": {
"react": "18.3.1",
"react-dom": "18.3.1"
},
"overrides": {
"react": "$react",
"react-dom": "$react-dom"
}
}
$react这种写法是引用dependencies里声明的那个版本,两边永远同步,不用手改两处。也可以写死,或者只针对某一个父包生效:
"overrides": {
"some-old-plugin": {
"react": "18.3.1"
}
}
实测最常见的是安全扫描要求把某个深层依赖升到修复版本,而父包把范围锁死了,这时候overrides一行就够,不用去动别人的包。改完记得删掉node_modules和lock重跑一次,否则旧树还在。
6、用npm ls和npm explain找来源
npm ls react npm explain react
npm ls列出整棵树上谁依赖了react、各自是什么版本;npm explain说明为什么最终装的是这个版本、是谁把它拽进来的。冲突的源头经常不在项目直接写的那几个依赖里,而是三层以下某个包传递出来的。
7、清理重来
改完package.json直接install,npm有时不会重算冲突,lock里还留着旧记录。删掉重装最省事:
rm -rf node_modules package-lock.json npm install
Windows上在资源管理器里删node_modules经常因为路径太长失败,用rimraf更稳:
npx rimraf node_modules package-lock.json
8、--force和 --legacy-peer-deps不是一回事
--legacy-peer-deps只放宽peer依赖这一条规则,--force是把所有解析错误都吞掉,包括本该报错的那些,装出来的树更不可控。要用先用前者。
9、monorepo里多一层限制
workspaces会把所有子包的依赖提升到根node_modules,所以子包之间对同一个包的版本要求必须一致。A包要react 17、B包要react 18,npm没法在一棵树上同时满足,报的同样是ERESOLVE,只是开头多了While resolving workspace dependencies这一行。要么统一版本,要么在根package.json用overrides钉死。
整篇走下来,处理顺序就是:读报错定位冲突的两个包,能升则升,升不了用overrides钉版本,赶时间才动 --legacy-peer-deps。把legacy-peer-deps=true写进 .npmrc之前想清楚,那个开关一开,后面所有真实冲突都不会再提醒你了。数据截至2026年9月。