npm ERR! ERESOLVE unable to resolve dependency tree 해결: peer dependency 충돌 읽는 법과 --legacy-peer-deps 대신 할 일
이 글의 핵심
npm 7부터는 peer dependency 범위가 맞지 않으면 npm install이 ERESOLVE unable to resolve dependency tree로 멈춥니다. 대부분의 검색 결과는 --legacy-peer-deps를 붙이라고 하지만, 이것은 오류를 고치는 것이 아니라 검사를 끄는 것입니다. 이 글은 오류 출력의 While resolving·Found·Could not resolve dependency 줄이 각각 무엇을 뜻하는지 읽는 법을 먼저 설명하고, 충돌을 일으킨 패키지 업그레이드, 호환 버전 선택, package.json overrides, --legacy-peer-deps와 .npmrc, --force 순으로 해결법과 각각의 대가를 정리합니다. node_modules와 package-lock.json을 지우는 것이 왜 어떤 때는 통하고 어떤 때는 아무 효과가 없는지도 다룹니다.
ERESOLVE는 npm이 “이 조합은 보장할 수 없다”고 멈추는 것이다
npm install을 실행했는데 수십 줄의 붉은 출력과 함께 이런 메시지가 나오는 경우입니다.
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm 10의 최근 버전에서는 접두어가 npm ERR! 대신 npm error로 바뀌어 npm error code ERESOLVE로 출력되지만 의미는 같습니다.
이 오류는 거의 항상 peer dependency 충돌입니다. peer dependency는 “나는 이 패키지를 직접 설치하지 않지만, 나를 쓰는 프로젝트에 이 패키지의 이 범위 버전이 있어야 한다”는 선언입니다. React 컴포넌트 라이브러리가 대표적입니다. 라이브러리가 React를 자기 의존성으로 따로 설치하면 앱에 React가 두 벌 들어가고, React는 인스턴스가 둘이면 Hook이 동작하지 않습니다. 그래서 라이브러리는 peerDependencies에 "react": "^17.0.0 || ^18.0.0"처럼 적어 “앱이 가진 React를 같이 쓰겠다, 단 이 범위여야 한다”고 말합니다.
문제는 앱의 React가 그 범위 밖일 때입니다. 예를 들어 앱은 React 19를 쓰는데 라이브러리가 18까지만 지원한다고 선언했다면, 조건을 모두 만족하는 의존성 트리가 존재하지 않습니다.
npm 6까지는 이런 경우 경고만 띄우고 설치를 진행했습니다. peer dependency를 자동으로 설치하지도 않았기 때문에 개발자가 경고를 보고 알아서 맞춰야 했고, 실제로는 경고가 무시되는 일이 많았습니다. npm 7부터는 peer dependency를 자동으로 설치하고, 범위가 맞지 않으면 설치를 거부하도록 바뀌었습니다. 그래서 몇 년 전에 잘 설치되던 프로젝트가 새 Node(따라서 새 npm)에서 갑자기 ERESOLVE를 내는 일이 흔합니다. 바뀐 것은 프로젝트가 아니라 npm의 검사 기준입니다.
기준 환경: Node.js 22 LTS에 포함된 npm 10. npm 7~9에서도 오류 형식과 해결법은 거의 같습니다. 현재 버전은 npm -v로 확인합니다. Node 버전을 바꿔 가며 확인해야 한다면 nvm으로 Node.js 버전 변경하기를 참고하세요.
오류 출력을 한 줄씩 읽는 법
아래는 형식을 설명하기 위한 예시입니다. legacy-ui-kit은 가상의 패키지 이름이고, 앱이 React 19를 쓰는데 이 라이브러리가 React 16.8~18만 지원한다고 선언한 상황입니다.
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR! react@"^19.0.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.8.0 || ^17.0.0 || ^18.0.0" from [email protected]
npm ERR! node_modules/legacy-ui-kit
npm ERR! legacy-ui-kit@"^3.2.1" from the root project
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 ERR!
npm ERR!
npm ERR! For a full report see:
npm ERR! /home/<사용자>/.npm/_logs/2026-10-20T01_23_45_678Z-eresolve-report.txt
각 부분의 의미는 다음과 같습니다.
While resolving: [email protected]: 어느 패키지의 의존성을 계산하다가 막혔는지입니다. 루트 프로젝트 이름이 나오면 내package.json에 직접 적은 의존성끼리 충돌한다는 뜻이고, 다른 패키지 이름이 나오면 그 패키지 내부의 의존성에서 막힌 것입니다.Found: [email protected]와 그 아래 줄: 현재 트리에 이미 자리 잡은(또는 자리 잡을 예정인) 버전과, 그 버전이 왜 거기 있는지입니다.react@"^19.0.0" from the root project는 “내 package.json이 ^19.0.0을 요구해서 19.0.0이 들어갔다”는 뜻입니다.Could not resolve dependency:아래의peer react@"..." from [email protected]: 충돌의 상대편입니다.legacy-ui-kit3.2.1이 peer로 요구하는 React 범위가 적혀 있습니다. 앞의peer라는 단어가 이것이 peer dependency 충돌임을 알려 줍니다.- 그 아래
legacy-ui-kit@"^3.2.1" from the root project: 그 라이브러리가 왜 설치되는지입니다. 여기서도 root project가 나오면 내가 직접 추가한 패키지라는 뜻이므로, 내가 버전을 직접 고를 수 있습니다.
정리하면 이 오류는 “루트가 원하는 React 19와, 루트가 원하는 legacy-ui-kit 3.2.1이 원하는 React 16.8~18을 동시에 만족할 수 없다”는 한 문장입니다. 출력이 길어 보여도 결국 Found 쪽 버전과 peer 요구 범위 두 개를 비교하면 됩니다.
출력의 변형도 알아 두면 좋습니다. 충돌이 루트가 아니라 깊은 의존성 사이에서 생기면 ERESOLVE could not resolve라는 제목에 Conflicting peer dependency: [email protected] 같은 줄이 추가로 나옵니다. 이것은 “이 peer 요구를 만족시키려면 react 18.3.1이 필요한데 이미 다른 버전이 있다”는 뜻입니다. 또 오류가 아니라 npm WARN ERESOLVE overriding peer dependency라는 경고만 나오고 설치가 끝나는 경우도 있는데, 이것은 npm이 깊은 의존성의 충돌을 보고 스스로 한쪽을 택해 진행했다는 의미입니다.
마지막 줄의 eresolve-report.txt에는 화면에 다 못 보여 준 전체 트리 정보가 들어 있습니다. 화면 출력만으로 어떤 경로로 충돌이 생기는지 모르겠으면 이 파일을 엽니다. 윈도우에서는 보통 C:\Users\me\AppData\Local\npm-cache\_logs\ 아래에 있습니다.
충돌 상대를 정확히 확인하는 명령
해결법을 고르기 전에 두 가지를 확인합니다. 충돌하는 라이브러리의 새 버전이 내 React 버전을 지원하는지, 그리고 지금 트리에서 해당 패키지가 어떤 경로로 들어오는지입니다.
# 최신 버전이 요구하는 peer 범위
npm view legacy-ui-kit peerDependencies
# 특정 버전의 peer 범위
npm view [email protected] peerDependencies
# 배포된 모든 버전 목록
npm view legacy-ui-kit versions --json
이미 node_modules가 있는 프로젝트라면 어떤 패키지가 react를 끌어오는지도 볼 수 있습니다.
npm ls react
npm explain react
npm explain은 “이 패키지가 왜 설치되었는지”를 의존 경로로 보여 주므로, 깊은 의존성에서 생긴 충돌을 추적할 때 특히 유용합니다.
해결 1: 충돌을 일으킨 패키지를 업그레이드한다
가장 먼저 시도할 방법이고, 실제로 가장 많이 통하는 방법입니다. React, Vue, ESLint, TypeScript처럼 생태계 중심 패키지의 메이저 버전이 나오면 플러그인과 라이브러리가 몇 주에서 몇 달 뒤에 peer 범위를 넓힌 새 버전을 냅니다. 오류의 원인은 내 package.json에 적힌 라이브러리 버전이 그 이전 것이기 때문인 경우가 많습니다.
npm view legacy-ui-kit peerDependencies
# { react: '^18.0.0 || ^19.0.0' } ← 최신 버전은 19를 지원한다면
npm install legacy-ui-kit@latest
메이저 버전이 올라가는 업그레이드라면 변경 사항(CHANGELOG, 마이그레이션 가이드)을 확인해야 합니다. 그래도 이 방법은 라이브러리 작성자가 “이 조합은 동작한다”고 확인한 버전을 쓰는 것이므로, 아래의 우회 방법들보다 훨씬 안전합니다.
최신 버전도 아직 지원하지 않는다면 저장소의 이슈와 PR을 확인해 봅니다. “Support React 19” 같은 제목의 이슈에 지원 일정이나, 실제로 써 보니 잘 동작한다는 사용자 보고가 있는 경우가 많습니다. 이 정보가 아래 3번이나 4번 방법을 쓸지 판단하는 근거가 됩니다.
해결 2: 반대편을 호환되는 버전으로 맞춘다
라이브러리가 아직 새 버전을 지원하지 않고, 내가 그 새 버전을 꼭 써야 하는 이유가 없다면 반대로 중심 패키지 버전을 내리는 것이 맞습니다. 새 프로젝트 템플릿이 최신 React를 기본으로 설치했는데, 꼭 쓰고 싶은 라이브러리가 아직 18까지만 지원하는 경우가 전형적입니다.
npm install react@18 react-dom@18
react와 react-dom처럼 짝으로 움직이는 패키지는 함께 맞춰야 합니다. 하나만 내리면 이번에는 둘 사이에서 peer 충돌이 납니다. @types/react를 쓴다면 그 버전도 함께 맞춥니다.
이 방법의 대가는 새 버전의 기능을 당분간 쓰지 못한다는 것입니다. 대신 모든 패키지가 서로 지원한다고 선언한 조합이므로 런타임에 예상하지 못한 문제가 생길 가능성이 가장 낮습니다.
해결 3: package.json의 overrides로 특정 경로만 바꾼다
라이브러리 작성자가 peer 범위를 보수적으로 적었을 뿐 실제로는 새 버전에서도 잘 동작한다는 확인(이슈의 보고, 직접 테스트)이 있다면, npm 8.3부터 지원하는 overrides로 그 부분만 바꿀 수 있습니다.
{
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"legacy-ui-kit": "^3.2.1"
},
"overrides": {
"legacy-ui-kit": {
"react": "$react",
"react-dom": "$react-dom"
}
}
}
"$react"는 “루트 dependencies에 적힌 react 버전을 그대로 쓰라”는 참조 문법입니다. 버전을 두 군데에 적지 않아도 되므로 나중에 React를 올릴 때 overrides를 잊고 남겨 두는 실수를 줄일 수 있습니다. 이 참조를 쓰려면 해당 패키지가 루트의 직접 의존성이어야 합니다.
overrides를 바꾼 뒤에는 lock 파일이 새 규칙으로 다시 계산되도록 npm install을 실행하고, npm ls react로 트리에 React가 한 벌만 있는지 확인합니다.
--legacy-peer-deps와 비교한 overrides의 장점은 범위가 좁다는 것입니다. legacy-ui-kit 한 곳의 요구만 바꾸고 나머지 모든 패키지의 peer 검사는 그대로 살아 있습니다. 그리고 package.json에 남으므로 팀원과 CI가 같은 결과를 얻고, 나중에 리뷰하는 사람도 “여기서 의도적으로 규칙을 바꿨다”는 것을 볼 수 있습니다.
주의할 점도 있습니다. overrides는 “이 버전으로도 동작한다”는 판단을 내가 대신 내리는 것이므로, 라이브러리가 실제로 새 버전에서 제거된 API를 쓰고 있다면 런타임에 깨집니다. 또 overrides에 적은 버전이 루트 직접 의존성과 다르면 npm이 EOVERRIDE 오류로 거부하므로, 위처럼 $ 참조를 쓰는 것이 편합니다. 일부 복잡한 peer 충돌은 overrides만으로 풀리지 않는 경우도 있어, 적용 후에도 ERESOLVE가 남으면 출력의 While resolving 대상이 바뀌었는지 다시 읽어야 합니다. 그리고 라이브러리가 공식 지원 버전을 내면 overrides를 지우는 것을 잊지 마세요.
Yarn은 같은 기능을 resolutions, pnpm은 pnpm.overrides라는 다른 필드로 제공합니다. npm의 overrides는 npm에서만 읽습니다.
해결 4: —legacy-peer-deps와 그 대가
검색하면 가장 먼저 나오는 방법입니다.
npm install --legacy-peer-deps
이 옵션은 npm 6처럼 동작하라는 뜻입니다. 구체적으로는 peer dependency를 아예 무시합니다. 범위 검사를 하지 않을 뿐 아니라 peer dependency를 자동으로 설치하지도 않습니다. 그래서 오류는 사라지지만 다음과 같은 일이 생길 수 있습니다.
- 모든 패키지의 peer 검사가 꺼집니다. 지금 문제인
legacy-ui-kit하나뿐 아니라, 앞으로 추가할 모든 패키지의 진짜 비호환도 경고 없이 설치됩니다. - peer dependency가 자동 설치되지 않으므로, 라이브러리가 peer로 요구하는 패키지를 내가 직접 설치하지 않았다면 실행할 때
Cannot find module오류가 납니다. npm 7 이상의 자동 설치에 익숙한 사람이 놓치기 쉬운 부분입니다. - 검사 없이 만든
package-lock.json은 이 옵션 없이 실행하는 환경(다른 팀원, CI의npm ci)에서 다시 ERESOLVE를 낼 수 있습니다.
그래서 이 옵션을 쓸지는 이렇게 판단하는 것이 좋습니다. 위의 13번이 모두 불가능하고, 충돌하는 라이브러리가 실제로 동작한다는 근거가 있고, 프로젝트의 모든 개발자와 CI에 같은 설정을 강제할 수 있을 때입니다. 막혀서 일단 넘어가는 용도로 쓴다면 나중에 반드시 13번으로 바꿀 일을 남겨 둔 것임을 기억해야 합니다.
.npmrc에 legacy-peer-deps를 넣는 경우
매번 옵션을 붙이는 대신 프로젝트 루트의 .npmrc에 설정을 넣을 수 있습니다.
legacy-peer-deps=true
이 파일을 저장소에 커밋하면 팀원과 CI가 같은 규칙으로 설치하므로 앞의 “로컬에서는 되는데 CI에서만 실패” 문제를 막을 수 있습니다. 반대로 npm config set legacy-peer-deps true로 사용자 전역 설정(~/.npmrc)에 넣는 것은 권장하지 않습니다. 내 컴퓨터의 모든 프로젝트에서 peer 검사가 꺼지고, 나만 설치가 되고 다른 사람은 안 되는 원인을 찾기 어렵게 만듭니다. 현재 어떤 값이 어디서 적용되는지는 다음으로 확인합니다.
npm config get legacy-peer-deps
npm config list
npm config list 출력에는 각 설정이 어느 .npmrc 파일에서 왔는지 표시됩니다.
해결 5: —force는 무엇이 다른가
npm install --force
--force는 peer 검사를 끄는 것이 아니라, 충돌을 발견해도 npm이 한쪽을 골라 강제로 진행하게 합니다. peer dependency 자동 설치는 그대로 유지되므로, 겉보기에는 --legacy-peer-deps보다 덜 극단적으로 보입니다. 하지만 어떤 버전이 선택될지를 내가 정하지 않고 npm에 맡기는 것이라 결과를 예측하기 어렵습니다. 또 --force는 peer 충돌만이 아니라 캐시를 무시하고 다시 받는 등 여러 안전장치를 함께 끄는 범용 옵션이라, 이 오류 하나를 넘기려고 쓰기에는 영향 범위가 넓습니다.
선택지 중 마지막에 두는 이유가 이것입니다. 실제로 쓸 일은 거의 없고, 쓴다면 어떤 버전이 설치되었는지 npm ls로 반드시 확인해야 합니다.
node_modules와 package-lock.json을 지우면 해결될까
“rm -rf node_modules package-lock.json 후 다시 설치하세요”라는 조언도 자주 보입니다. 이것은 원인에 따라 효과가 완전히 다릅니다.
효과가 있는 경우는 충돌이 lock 파일이나 기존 트리에 남은 오래된 버전 때문에 생길 때입니다. 예를 들어 package.json에서는 legacy-ui-kit을 19를 지원하는 ^4.0.0으로 올렸는데, lock 파일과 node_modules에는 3.x가 남아 있어 그것과 충돌하는 경우입니다. 이때 출력의 Found: 줄이 node_modules/...에 있는 예전 버전을 가리킵니다. 트리를 처음부터 다시 계산하게 하면 충돌이 사라집니다.
효과가 없는 경우는 package.json에 적힌 범위 자체가 서로 맞지 않을 때입니다. 위 예시처럼 react ^19와 “react 18까지만 지원하는 legacy-ui-kit ^3.2.1”을 함께 요구한다면, 아무것도 없는 상태에서 다시 계산해도 답이 없으므로 똑같은 ERESOLVE가 납니다.
그리고 lock 파일을 지우는 것에는 대가가 있습니다. lock 파일은 모든 의존성의 정확한 버전을 고정하는 파일이라, 지우고 다시 설치하면 package.json의 범위 안에서 모든 패키지가 그 시점 최신 버전으로 바뀝니다. ERESOLVE는 풀렸는데 관계없는 패키지가 함께 올라가서 다른 곳이 깨지는 일이 생길 수 있습니다. 그래서 순서는 이렇게 하는 것이 좋습니다.
node_modules만 지우고npm install을 다시 실행합니다. lock 파일은 유지됩니다.- 그래도 안 되면 오류의
Found:줄이 무엇을 가리키는지 확인하고, 해당 패키지만npm install 패키지@버전으로 갱신합니다. 그러면 lock 파일의 다른 부분은 그대로 남습니다. - lock 파일 전체를 다시 만드는 것은 마지막에, 별도 커밋으로 하고, 다시 만든 뒤 테스트를 돌립니다.
권한이나 캐시 손상 같은 다른 원인으로 설치가 실패하는 경우는 오류 코드가 다르므로 npm install 에러 해결 10가지에서 해당 코드를 찾아보세요.
우회를 걸어 두었다면 언제 되돌릴지 정해 둔다
overrides나 legacy-peer-deps로 넘어갔다면, 그 설정은 “언젠가 지워야 할 임시 조치”입니다. 아무도 기억하지 못한 채 남아 있으면 다음 메이저 업그레이드 때 더 큰 충돌의 원인이 됩니다. 몇 가지 습관이 도움이 됩니다.
- overrides를 넣은 커밋 메시지나 PR 설명에 원인이 된 라이브러리의 이슈 링크를 남깁니다. 나중에 그 이슈가 닫혔는지만 보면 되돌릴 시점을 알 수 있습니다.
npm outdated를 주기적으로 실행해, 충돌하던 라이브러리에 새 버전이 나왔는지 확인합니다. 새 버전이 나오면npm view 패키지 peerDependencies로 범위가 넓어졌는지 봅니다.- 되돌릴 때는 overrides나
.npmrc설정을 지운 뒤npm install을 실행해 ERESOLVE 없이 설치되는지 확인합니다. 오류 없이 통과하면 npm의 기본 검사 아래에서 모든 조합이 공식적으로 호환된다는 뜻이므로 그때 우회 설정을 완전히 제거하면 됩니다.
이렇게 해 두면 ERESOLVE는 귀찮은 오류가 아니라 “의존성 중 하나가 아직 생태계 변화를 따라오지 못했다”는 신호로 다룰 수 있습니다.