npm ERESOLVE unable to resolve dependency tree는 코드가 아니라 어떤 패키지가 요구하는 피어 버전과 설치된 버전이 어긋나서 난다. npm 7부터 피어 의존성을 설치 시점에 엄격 검사하기 때문이다. npm explain·npm ls로 충돌 출처를 찾고, 버전 맞추기와 package-lock 재생성, npm 8.3 overrides로 전이 의존성 고정, --legacy-peer-deps와 --force의 차이와 위험까지. 개발자, Next.js, 리액트, 프론트엔드, npm, 오픈소스 기준. 2026년 8월 npm 공식 문서.
npm 설치가 ERESOLVE unable to resolve dependency tree로 멈춘다면, 코드가 틀린 게 아니라 어떤 패키지가 요구하는 피어 버전과 지금 설치된 버전이 서로 어긋난 것입니다. 그래서 다시 설치만 반복해서는 풀리지 않습니다.
리액트를 17에서 18로 올리다가, 또는 오래된 UI 라이브러리를 하나 추가하다가 빨간 에러 벽을 만난 경험, 한 번쯤 있으실 겁니다. 검색하면 --force나 --legacy-peer-deps를 붙이라는 답이 제일 먼저 나오죠. 그런데 그 플래그가 정확히 뭘 하는지 모르고 쓰면, 설치는 되는데 실행할 때 화면이 하얗게 뜨는 더 골치 아픈 상황으로 넘어갑니다.
아래에서는 ERESOLVE가 왜 npm 7부터 갑자기 엄격해졌는지, 충돌의 범인을 어떻게 한 줄 명령으로 찾는지, 그리고 버전 맞추기부터 overrides, --legacy-peer-deps, --force까지 어떤 순서로 골라야 안전한지를 하나씩 풀어 봅니다. 한국에서 프런트엔드나 풀스택을 혼자 붙잡고 계신 분이라면 배포 직전에 자주 만나는 벽이죠. 같은 에러 앞에서 무엇부터 눌러야 할지 막막하셨다면 도움이 되실 거예요. 근거는 npm 공식 문서에 뒀습니다.
ERESOLVE 에러는 무엇을 막는 건가
ERESOLVE는 npm이 모든 버전 규칙을 동시에 만족하는 의존성 트리를 만들지 못했을 때 뜨는 에러입니다. 대부분은 피어 의존성(peer dependency) 때문이에요. 어떤 패키지가 "나를 쓰려면 리액트 18이 옆에 있어야 한다"고 선언했는데, 프로젝트에는 리액트 17이 박혀 있으면 npm이 트리를 못 짜고 설치를 중단합니다.
이게 갑자기 나타난 이유는 npm 버전이 바뀌었기 때문입니다. npm 6까지는 피어 의존성이 안 맞아도 경고만 띄우고 그냥 설치했어요. 그런데 npm 7부터는 피어 의존성을 설치 시점에 엄격하게 검사해서, 트리가 어긋나면 아예 실패로 처리합니다. 오래 쓰던 프로젝트를 최신 노드로 옮기거나 새 노트북에서 npm install을 돌렸을 때 없던 에러가 튀어나오는 배경이 여기 있습니다.
먼저 짚어 둘 점은, 이 엄격함이 나쁘기만 한 건 아니라는 것입니다. 예전에는 그냥 설치되던 어긋난 조합이 실행 중에 이상하게 깨졌다면, 이제는 설치 단계에서 미리 걸러 주는 셈이죠. 그러니 플래그로 검사를 꺼 버리기 전에, 무엇이 무엇과 부딪히는지부터 읽는 편이 결국 빠릅니다.
에러 메시지부터 끝까지 읽기 | ERESOLVE 출력에는 항상 Found: 뒤에 지금 설치된 버전이, Could not resolve dependency: 뒤에 어떤 패키지가 어떤 피어 버전을 원하는지 적혀 있습니다. 이 두 줄만 대조해도 범인의 절반이 보입니다.
npm 7부터는 피어 의존성이 어긋나면 설치 자체가 실패로 끝난다
충돌의 범인을 찾는 진단 명령
무엇을 고칠지 정하려면 누가 어떤 버전을 요구하는지부터 밝혀야 합니다. 플래그로 덮기 전에 아래 명령을 먼저 돌리면 대개 1분 안에 원인이 드러납니다. 핵심은 npm explain과 npm ls 두 가지예요.
npm ls 패키지이름은 그 패키지가 트리 어디에 몇 번 버전으로 들어와 있는지 보여 줍니다. 같은 패키지가 서로 다른 버전으로 두 번 이상 나온다면 그게 충돌의 신호죠. npm explain 패키지이름은 한 걸음 더 들어가서, 그 버전이 왜 필요한지 부모 패키지까지 거슬러 설명해 줍니다. 에러에 적힌 피어 요구가 실제로 어느 라이브러리에서 나온 건지 여기서 확정할 수 있습니다.
명령
언제 쓰나
확인할 것
npm ls react
중복 설치 의심
같은 패키지 여러 버전 동시 존재
npm explain 패키지
피어 요구 출처 추적
어느 부모가 어떤 버전을 요구하나
npm install (출력 그대로)
에러 전문 읽기
Found 버전 vs peer 요구 버전
한 가지 더 흔한 원인이 있습니다. 락파일이 어중간하게 갱신된 경우예요. package.json은 손으로 고쳤는데 package-lock.json은 옛 트리를 그대로 담고 있으면, 깨끗한 설치에서 npm이 두 기록을 화해시키지 못하고 ERESOLVE로 떨어집니다.
충돌 진단 명령 예시
# 어떤 버전이 어디에 들어와 있는지
npm ls react
# 그 버전이 왜 필요한지 부모까지 추적
npm explain @some/ui
# 에러 전문을 잘리지 않게 보기
npm install 2>&1 | head -50
가장 안전한 해결, 버전 맞추기
제일 오래가는 해결은 충돌하는 버전을 서로 맞추는 것입니다. 에러 메시지에서 어느 쪽이 요구 버전이고 어느 쪽이 설치 버전인지 읽은 다음, 한쪽을 올리거나 내려서 같은 메이저 버전대로 맞춥니다. 예를 들어 라이브러리가 리액트 18을 요구하는데 프로젝트가 17에 묶여 있다면, 리액트를 18로 올리거나 그 라이브러리를 17을 지원하는 버전으로 낮추는 식이죠.
버전을 손봤다면 락파일을 새로 만들어 줘야 합니다. package-lock.json과 node_modules를 지우고 다시 설치하면, npm이 바뀐 package.json 기준으로 트리를 처음부터 다시 계산합니다. 새로 생긴 락파일은 반드시 커밋해서 다른 환경과 CI가 같은 트리를 쓰도록 맞추세요.
이 방식이 번거로워 보여도, 설치가 CI와 배포, 앞으로의 업그레이드까지 무사히 넘어가게 해 주는 유일한 근본 처방입니다. 플래그로 검사를 끄는 방법들은 이 다음에 나오지만, 순서상 항상 버전 맞추기를 먼저 시도하는 편이 좋습니다.
버전 맞추고 락파일 재생성
# 충돌하는 쪽 버전을 맞춘 뒤
rm -rf node_modules package-lock.json
npm install
# 팀·CI가 같은 트리를 쓰도록 락파일 커밋
git add package-lock.json package.json
git commit -m "fix: align peer dependency versions"
버전을 맞춘 뒤에는 락파일을 새로 만들어 커밋해야 CI까지 안전하다
overrides로 전이 의존성 고정하기
내가 직접 설치한 패키지가 아니라, 그 안쪽에 딸려 온 전이 의존성이 엉뚱한 버전을 끌고 올 때가 있습니다. 이럴 때는 루트 package.json에 overrides를 두면 특정 패키지의 버전을 원하는 값으로 못 박을 수 있습니다. npm 8.3 이상에서 쓸 수 있는 기능이에요.
npm 공식 문서 기준으로 overrides는 보안 이슈가 있는 하위 의존성 버전을 바꾸거나, 트리 전체에서 같은 버전을 강제하고 싶을 때 쓰라고 안내합니다. 다만 규칙이 하나 있습니다. 내가 직접 의존하는 패키지에 대해서는, 선언한 스펙과 override 스펙이 완전히 같지 않으면 EOVERRIDE 에러가 납니다. 이걸 피하려고 "foo": "$foo"처럼 직접 의존성 스펙을 참조하는 문법도 제공합니다.
도구
필드
최소 버전
npm
overrides
npm 8.3 이상
Yarn
resolutions
Yarn 1 이상
pnpm
overrides
pnpm 7 이상
override는 --force보다 훨씬 통제된 방법입니다. 전체 검사를 끄는 게 아니라 딱 지목한 패키지 버전만 바꾸니까요. 대신 버전을 강제로 바꾼 만큼, 적용한 뒤에는 실제로 화면과 기능이 잘 도는지 한 번 돌려서 확인하는 절차가 필요합니다.
버전을 맞추기 어렵고 당장 막힌 걸 풀어야 할 때만 플래그를 씁니다. 그런데 --legacy-peer-deps와 --force는 하는 일이 다릅니다. 이걸 구분하지 않고 아무거나 붙이면 위험이 달라져요.
--legacy-peer-deps는 피어 의존성 검사만 npm 6 시절 방식으로 되돌립니다. 즉 피어 충돌을 무시하고 설치를 진행하죠. 반면 --force는 캐시와 체크섬을 포함한 거의 모든 안전 검사를 함께 건너뜁니다. 그래서 꼭 하나를 써야 한다면, 검사를 덜 끄는 --legacy-peer-deps 쪽이 상대적으로 덜 위험합니다.
두 플래그 모두 공통된 함정이 있습니다. 설치는 성공하지만 실제로는 어긋난 트리가 그대로 남는다는 점이에요. 설치 시점에 걸렸어야 할 문제가 실행 시점으로 미뤄지면서, 배포 후에 갑자기 화면이 깨지거나 알 수 없는 런타임 에러로 튀어나올 수 있습니다. 특히 전역 .npmrc에 legacy-peer-deps=true를 박아 두는 것은 피하세요. 모든 프로젝트에서 검사가 조용히 꺼진 채로 진행되기 때문입니다.
플래그
끄는 검사
위험도
--legacy-peer-deps
피어 의존성 검사만
중간, 임시 우회
--force
캐시·체크섬·피어 전부
높음, 최후 수단
overrides
검사 안 끄고 버전만 고정
낮음, 통제된 방식
[ ] 플래그를 붙이기 전에 npm explain으로 충돌 출처를 확인했다
[ ] 두 플래그 중 하나면 legacy-peer-deps를 먼저 골랐다
[ ] force는 정말 마지막 수단으로만 남겼다
[ ] 전역 .npmrc에 legacy-peer-deps=true를 박지 않았다
[ ] 플래그로 설치한 뒤 실행이 정상인지 직접 돌려 봤다
설치 성공이 곧 정상 동작은 아닙니다 | --force나 --legacy-peer-deps로 초록불이 떠도, 어긋난 트리는 그대로 남아 있습니다. 문제를 설치 단계에서 실행 단계로 미룬 것뿐이니, 임시 우회라는 사실을 팀에 공유하고 근본 수정 항목으로 남겨 두세요.
force는 캐시와 체크섬까지 끄므로 legacy-peer-deps보다 더 위험하다
실무에서 고르는 순서
막힌 순간에 순서를 정해 두면 헤매지 않습니다. 안전한 쪽부터 시도하고, 그게 안 될 때만 다음 단계로 내려가는 흐름이 좋습니다. 처음에는 에러 전문을 읽고 npm explain으로 범인을 찾습니다. 그다음 충돌하는 버전을 맞추고 락파일을 재생성하죠. 직접 의존이 아니라 전이 의존성이 문제라면 overrides로 그 버전만 고정합니다. 여기까지 안 되면 그때 비로소 --legacy-peer-deps를 임시로 얹고, --force는 정말 마지막으로 남깁니다.
한국에서 리액트나 Next.js 프로젝트를 혼자 운영하시는 분이라면, 배포 파이프라인이 npm ci로 도는 경우가 많습니다. npm ci는 락파일을 그대로 신뢰해서 설치하기 때문에, 로컬에서 플래그로 대충 넘긴 트리가 CI에서 똑같이 재현됩니다. 그래서 로컬에서 --legacy-peer-deps로 넘겼다면 락파일도 그 상태로 커밋되어, 배포 환경에서도 같은 어긋남을 안고 갑니다. 근본 수정을 미루면 결국 배포 때 되돌아온다는 뜻이죠.
업그레이드를 자동화하고 싶다면 디펜다봇이나 리노베이트 같은 도구로 피어 패키지를 함께 올리는 방법도 있습니다. 리액트와 그에 딸린 UI 라이브러리를 한 묶음으로 올려 두면, 한쪽만 올라가서 피어가 어긋나는 상황 자체가 줄어듭니다. 보안 헤더나 배포 설정이 얽힌 이야기는 보안 헤더 글과 함께 보시면 됩니다.
npm 6까지는 피어 의존성이 안 맞아도 경고만 띄우고 설치를 진행했습니다. npm 7부터 피어 의존성을 설치 시점에 엄격하게 검사하도록 바뀌면서, 트리를 못 짜면 실패로 처리합니다. 오래된 프로젝트를 최신 노드로 옮길 때 없던 에러가 나타나는 이유입니다.
--force와 --legacy-peer-deps 중 뭘 써야 하나요?
꼭 하나를 써야 한다면 --legacy-peer-deps가 상대적으로 덜 위험합니다. 이 옵션은 피어 검사만 끄지만, --force는 캐시와 체크섬을 포함한 거의 모든 안전 검사를 함께 건너뜁니다. force는 정말 마지막 수단으로만 남기세요.
overrides는 언제 쓰는 게 맞나요?
내가 직접 설치한 게 아니라 그 안쪽에 딸려 온 전이 의존성이 엉뚱한 버전을 끌고 올 때 씁니다. 루트 package.json에 overrides로 그 패키지 버전만 고정하면 되고, npm 8.3 이상에서 동작합니다. 전체 검사를 끄는 --force보다 통제된 방식입니다.
플래그로 설치했는데 나중에 화면이 깨져요.
전형적인 증상입니다. --force나 --legacy-peer-deps는 설치를 통과시키지만 어긋난 트리는 그대로 남습니다. 설치 단계에서 걸렸어야 할 문제가 실행 단계로 미뤄진 것이죠. 근본 해결은 충돌 버전을 맞추고 락파일을 재생성하는 것입니다.
전역 .npmrc에 legacy-peer-deps=true를 넣어도 되나요?
권하지 않습니다. 전역에 박으면 모든 프로젝트에서 피어 검사가 조용히 꺼진 채로 설치가 진행됩니다. 어긋난 조합을 걸러 주는 안전망이 통째로 사라지는 셈이라, 문제가 있는 프로젝트에서만 임시로 붙이는 편이 안전합니다.
npm ci와 npm install은 이 에러에서 뭐가 다른가요?
npm ci는 락파일을 그대로 신뢰해 설치합니다. 그래서 로컬에서 플래그로 넘긴 트리가 락파일에 담겨 커밋되면, CI에서도 똑같은 어긋남이 재현됩니다. 로컬에서 임시로 넘겼더라도 근본 수정을 하지 않으면 배포 환경에서 같은 문제를 안고 가게 됩니다.
ERESOLVE는 결국 어떤 패키지가 요구하는 피어 버전과 지금 트리가 어긋났다는 신호입니다. 플래그로 검사를 끄기 전에 npm explain으로 범인을 찾고, 버전을 맞춘 뒤 락파일을 재생성하는 순서가 가장 오래갑니다. 전이 의존성만 문제라면 overrides로 그 버전만 고정하면 되죠. 관련 글: CORS 에러와 프리플라이트, 보안 헤더와 CSP.