본문으로 건너뛰기
개발 머꼬
개발 노트Node.js
hohyeon.dev2

npm install이 ERESOLVE unable to resolve dependency tree로 멈춘 이유

  • #Common Pitfall
  • #Engineering Note
  • #npm

문제 발생

프로젝트를 React 19로 올리고 나서, 예전부터 쓰던 날짜 선택 라이브러리를 새 화면에 하나 더 쓰려고 설치했습니다. 그런데 npm install이 아무것도 설치하지 않고 바로 멈췄습니다.

npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: [email protected]
npm error Found: [email protected]
npm error node_modules/react
npm error   react@"^19.1.0" from the root project
npm error
npm error Could not resolve dependency:
npm error peer react@"^16.8.0 || ^17.0.0 || ^18.0.0" from [email protected]
npm error node_modules/old-datepicker
npm error   old-datepicker@"^4.2.0" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.

검색해 보면 거의 모든 답이 "--legacy-peer-deps 붙이면 됩니다"였습니다. 붙이니 정말 설치는 됐습니다. 문제는 그다음이었습니다. 제 컴퓨터에서는 잘 되는데, CI의 npm ci가 실패했고, 새로 합류한 동료도 같은 저장소를 받아서 npm install을 하자 막혔습니다. 플래그 하나로 넘긴 문제가 팀 전체로 번진 겁니다.

원인 분석

에러 메시지가 원인을 거의 다 말해 줍니다. 위 메시지를 위에서부터 읽으면 이렇습니다. 지금 트리에는 [email protected]이 있고(Found), [email protected]은 peer react@"^16.8.0 || ^17.0.0 || ^18.0.0"을 요구하는데(Could not resolve dependency), 19는 이 범위에 들어가지 않습니다. 즉 "이 라이브러리는 React 16.8~18과 함께 쓰라고 선언돼 있는데, 프로젝트는 19를 쓰고 있다"는 뜻입니다. 이 글의 재현은 같은 peer 범위를 가진 로컬 패키지로 npm 10.9에서 했고, 위 메시지는 그때 찍힌 것에서 로컬 파일 경로만 버전 표기로 바꾼 것입니다.

npm 7부터 peer 의존성이 강제되기 시작했습니다. npm의 package.json 문서에 따르면 npm 3~6에서는 peerDependencies를 자동으로 설치하지 않고, 맞지 않는 버전이 있으면 경고만 했습니다. npm 7부터는 기본으로 설치하고, 충돌하는 요구가 있으면 트리를 제대로 풀 수 없어서 에러가 날 수 있다고 적혀 있습니다. 예전 프로젝트에서 경고 몇 줄로 지나가던 상황이 지금은 설치 자체를 막는 겁니다.

--legacy-peer-deps는 고치는 게 아니라 끄는 겁니다. npm 설정 문서에 따르면 이 옵션은 npm 3~6처럼 트리를 만들 때 peerDependencies를 완전히 무시하게 합니다. 같은 문서는 이 옵션이 의존 패키지가 기대하는 peer 의존성 계약을 지키지 않으니 권장하지 않는다고 적어 둡니다. 실제로 이 옵션으로 설치한 뒤 npm ls --all을 돌려 보니 충돌은 그대로 남아 있었습니다.

+-- [email protected]
| `-- [email protected] deduped invalid: "^16.8.0 || ^17.0.0 || ^18.0.0" from node_modules/old-datepicker
`-- [email protected] invalid: "^16.8.0 || ^17.0.0 || ^18.0.0" from node_modules/old-datepicker

npm error code ELSPROBLEMS

그리고 그 옵션은 내 터미널에만 적용됐습니다. 플래그를 붙여 만든 package-lock.json으로 npm ci를 플래그 없이 돌려 보니, npm은 lock대로 설치하지 않고 다시 ERESOLVE overriding peer dependency 경고를 내며 peer 의존성을 맞추려 들었습니다. 오프라인 재현이라 그다음 레지스트리 요청에서 멈췄지만, 같은 lock으로 npm ci --legacy-peer-deps를 돌리자 바로 설치됐습니다. CI와 동료 컴퓨터에서 막힌 이유가 이거였습니다. 저만 다른 규칙으로 설치하고 있었던 겁니다.

해결 방안

  1. 먼저 그 라이브러리의 새 버전이 지금 버전을 지원하는지 확인합니다. 제일 깔끔한 해결은 충돌 자체를 없애는 겁니다. npm view old-datepicker peerDependencies로 최신 버전의 peer 범위를 보고, 최신 버전이 ^19.0.0을 포함한다면 그 버전으로 올립니다. 재현에서 peer 범위가 ^18.0.0 || ^19.0.0인 5.0.0으로 올리자 플래그 없이 설치됐고, npm ls --all도 경고 없이 깨끗했습니다. 지원 버전이 아직 없다면 라이브러리 저장소의 이슈를 먼저 찾아봅니다. "React 19 지원" 같은 이슈에 실제로 써도 되는지, 대안 라이브러리는 무엇인지가 정리돼 있는 경우가 많습니다.

  2. 직접 써 보고 문제가 없다고 판단했다면 overrides로 그 패키지만 범위를 넓힙니다. 라이브러리가 peer 범위를 보수적으로 적어 뒀을 뿐 실제로는 19에서도 잘 도는 경우가 있습니다. 그럴 때는 전체를 끄는 대신 그 패키지 하나에만 예외를 둡니다. npm 문서에 따르면 overrides는 의존 트리의 패키지를 다른 버전으로 바꾸는 방법이고, 루트 package.json에서만 적용됩니다. 직접 의존하는 패키지를 가리킬 때는 이름 앞에 $를 붙여 그 명세를 참조할 수 있습니다.

{
  "dependencies": {
    "react": "^19.1.0",
    "old-datepicker": "^4.2.0"
  },
  "overrides": {
    "old-datepicker": {
      "react": "$react"
    }
  }
}

"old-datepicker 밑에서 react가 필요하면 루트의 react를 쓰라"는 뜻입니다. 이렇게 두고 플래그 없이 설치하니 ERESOLVE 없이 설치됐고, npm ls --all에서도 invalid 대신 overridden으로 표시됐습니다. --legacy-peer-deps와 달리 다른 패키지의 peer 검사는 그대로 살아 있고, 설정이 package.json에 남으니 CI와 동료에게도 똑같이 적용됩니다.

  1. --legacy-peer-deps를 써야 한다면 프로젝트의 .npmrc에 적어서 커밋합니다. 여러 패키지가 동시에 얽혀 있어서 당장 overrides로 정리하기 어려운 경우도 있습니다. 그럴 때 터미널에서 플래그만 붙이면 앞에서 본 것처럼 사람마다 다른 트리가 만들어집니다. 프로젝트 루트의 .npmrc에 legacy-peer-deps=true를 적어 두면 이 저장소에서 도는 모든 npm install과 npm ci가 같은 규칙을 씁니다. 재현에서도 .npmrc를 둔 뒤에는 플래그 없는 npm ci가 바로 성공했습니다. 다만 이건 임시방편이라, 1번이나 2번으로 정리할 이슈를 함께 남겨 둡니다.

  2. --force는 더 넓은 안전장치를 끈다는 걸 압니다. 에러 메시지가 함께 권하는 --force도 설치는 통과시킵니다. 하지만 npm 설정 문서를 보면 force는 "충돌하는 peer 의존성을 루트 프로젝트에 설치하도록 허용"하는 것 말고도, engines가 요구하는 Node 버전이 달라도 설치하게 하고, npm audit fix가 SemVer major 변경까지 설치하게 하는 등 여러 보호 장치를 한꺼번에 끕니다. 실행하면 npm warn using --force Recommended protections disabled.가 먼저 찍히는 이유입니다. peer 충돌 하나를 넘기려고 쓰기에는 범위가 너무 넓습니다.

  3. 라이브러리를 만드는 쪽이라면 peer 범위를 넓게 적습니다. npm의 package.json 문서는 peer 요구가 충돌하면 트리를 풀 수 없어 에러가 날 수 있으니, 요구 범위를 가능한 한 넓게 두고 특정 패치 버전으로 고정하지 말라고 권합니다. 사내 공용 컴포넌트 패키지가 "react": "18.2.0"처럼 정확한 버전을 peer로 적어 두면, 그걸 쓰는 모든 프로젝트가 React를 올리는 날 이 에러를 만납니다. 실제로 지원하는 범위를 ^18.0.0 || ^19.0.0처럼 적어 두는 것만으로 많은 팀의 시간을 아낄 수 있습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.