본문 바로가기

카테고리 없음

npm/yarn 설치 충돌·peer 디펜던시 완전 분해 가이드 (2025 최신)

 

한 줄 요약: npm install 또는 yarn install에서 버전 충돌/peer 오류가 터질 때, “왜” 생기는지 구조부터 이해하고, Node/패키지 매니저/의존 트리/호이스팅/lockfile 순서로 점검하면 90%가 즉시 해결된다. 이 글은 구글에 흩어진 팁들을 하나의 디버깅 루틴으로 묶어, 누구나 따라만 하면 해결되도록 만든 실전 가이드다.


0. 증상 미리 보기 (로그 판독법)

대표 에러 메시지

  • ERESOLVE unable to resolve dependency tree
  • peer dep missing: react@^18, required by some-lib@x.y.z
  • Found: react@17 but peer required react@^18
  • conflicting versions for... / dedupe failed / Package subpath './...' is not defined by "exports"
  • Yarn Berry(PnP): YN0002: │ some-pkg provides... which doesn't satisfy what... requests

👉 핵심은 누가 누구의 버전을 요구하는지를 읽어내는 것. required by / depends on / peer 키워드를 표시해 두면 원인 노드가 보인다.


1. 먼저 개념을 맞추자: dependencies vs peerDependencies

  • dependencies: 설치 시 트리 안에 직접 깔림. 소비자는 특별히 신경 안 써도 됨.
  • peerDependencies: “나와 같은 레벨(루트 또는 상위)에 이 버전이 있어야 해”라는 호환 계약. 예: UI 라이브러리(플러그인)가 react를 peer로 요구.
  • devDependencies: 빌드/테스트 도구 등 개발 시에만 필요.

npm 버전에 따른 차이 (중요)

  • npm v6: peer 자동 설치 안 함 → 경고만.
  • npm v7+: peer를 자동 해결하려고 시도 → 못 맞추면 ERESOLVE로 실패.
  • yarn v1: npm v6와 유사(노드_modules hoisting). resolutions로 강제 정합 가능.
  • yarn v2+ (Berry, PnP): 물리적 노드_modules 없음. 버전 불일치에 더 민감, 대신 정확한 제어 가능.

2. 최단 해결 루틴 (Check 1 → 7)

아래 순서로만 진행해도 대부분 해결된다. 각 단계는 빠르게 실패하고 다음 단계로 넘어가도록 설계했다.

✅ Check 1: Node & npm/yarn 버전 정합

  • node -v, npm -v 또는 yarn -v 확인.
  • . nvmrc/engines와 실제 런타임이 다른 경우가 잦다.
  • 해결: nvm use 혹은 nvm install <version>으로 프로젝트 권장 Node로 맞춤.

✅ Check 2: Lockfile·캐시 초기화

  • 단일 레포: rm -rf node_modules package-lock.json yarn.lock 후 재설치.
  • 모노레포/워크스페이스: 루트와 패키지들 모두 지우고 루트에서 재설치.
  • 캐시: npm cache verify / npm cache clean --force / yarn cache clean.

✅ Check 3: 최상위 peer 충족

  • 로그에서 peer... required... 찾기 → 루트 package.json에 직접 설치.
    • 예) react@18 요구 → npm i -S react@18 react-dom@18 (또는 yarn add react@18 react-dom@18).
  • peer는 대개 루트에. 하위 패키지에만 깔아도 해결 안 됨.

✅ Check 4: 범위 맞추기(semver)

  • ^18.2.0 vs ~18.2.0 vs 18.2.0 : 요구 범위가 좁으면 충돌.
  • 해결: 가능한 한 요구 범위를 수용하는 상위 버전으로 올리되, 브레이킹 여부는 릴리스 노트로 체크.

✅ Check 5: dedupe/overrides/resolutions

  • npm: npm dedupe로 트리 평탄화.
  • npm: overrides로 하위 의존 버전 강제 (npm v8+).
{
"overrides": { "some-lib/react": "^18.2.0", "left-pad": "1.3.0" }
}
  • yarn v1: resolutions 사용.
{
"resolutions": { "**/react": "18.2.0" }
}

 

  • yarn Berry: packageExtensions / resolutions / constraints 조합.

✅ Check 6: 임시 회피 플래그 (주의)

  • npm i --legacy-peer-deps (npm v7+의 peer 엄격 검사 비활성화)
  • npm i --force (권장 X: 트리 깨짐/런타임 버그 위험)
  • 배포 파이프라인에 상시 적용 금지. 로컬에서 원인 파악용으로만.

✅ Check 7: 대체 패키지/메이저 업그레이드

  • 오래된 라이브러리(메인테넌스 중단)가 최신 React/TS와 peer 충돌.
  • 포크/대체 패키지나 메이저 업그레이드 고려.

3. 상황별 실전 레시피

시나리오 A) React 17 프로젝트에 라이브러리가 react@^18 peer 요구

증상: Found: react@17.0.2 but peer required react@^18

해결 옵션

  1. 프로젝트 업그레이드: react@18로 올림(권장).
  2. 해당 라이브러리 다운그레이드: some-lib@<react17-compatible>.
  3. 임시 회피: --legacy-peer-deps (빌드/런타임 폭탄 가능, 장기 금지).

시나리오 B) Webpack/TS/ESLint 생태계 충돌

  • ESLint 플러그인들이 서로 특정 ESLint/TypeScript 버전 peer 요구.
  • 해결: 루트에 호환 매트릭스 맞춰 설치 → overrides/resolutions로 세부 패치 고정.
  • 예)
{
"devDependencies": {
"eslint": "^9",
"@typescript-eslint/parser": "^8",
"@typescript-eslint/eslint-plugin": "^8"
},
"overrides": {
"@typescript-eslint/*": { "eslint": "^9" }
}
}

시나리오 C) 모노레포(Workspaces)에서 패키지 간 React 중복

  • packages/a는 react@18, packages/b는 react@17.
  • 해결: 워크스페이스 루트에서 단일 React 버전으로 합의, 각 패키지 peerDependencies에 react: ">=18 <19"처럼 범위 통일.
  • Yarn Berry: constraints.pro로 일관성 강제.

시나리오 D) Yarn Berry(PnP)에서 PnP 규칙 충돌

  • 에러가 엄격하게 터짐.
  • 해결:. yarnrc.yml에 nodeLinker: node-modules로 임시 전환, 이후 점진적 PnP 적응 또는 packageExtensions로 누락된 peer 보강.
  • nodeLinker: node-modules
    packageExtensions:
    "some-lib@*":
    peerDependencies:
    react: ">=18"

4. 도구 세트(Commands & 툴)

  • 버전 인사이트: npx npm-why <pkg>, npx why <pkg>(yarn-why), npm ls <pkg>
  • 업데이트 후보: npx npm-check-updates -u → npm i / yarn upgrade-interactive --latest
  • 중복 제거: npm dedupe, yarn dedupe
  • 호환 리포트: ESLint/TS/React 각 릴리스 노트, peer 매트릭스 문서화
  • CI 재현: corepack enable로 Node별 npm/yarn 버전 고정, --frozen-lockfile/ci 모드 사용

5. 깔끔한 package.json 설계 패턴

  1. peer는 진짜 peer만: 플러그인/프레임워크가 공유해야 할 코어(react, next, eslint 등)만 peer로 선언.
  2. devDependencies로 개발 도구 격리: 앱 런타임에서 필요 없는 빌드 도구는 dev로.
  3. engines와 CI 동기화: "engines": { "node": ">=18 <21" } +. nvmrc.
  4. overrides/resolutions로 핫픽스 문서화: 임시 조치의 맥락을 주석/README에 기록.
  5. 모노레포는 루트 우선: 코어 버전(react, typescript 등)은 루트에만 설치, 각 패키지는 peer 범위만 명시.

6. 자주 묻는 질문(FAQ)

Q. --legacy-peer-deps를 항상 써도 되나요?

  • A. 비추천. 설치는 되지만 런타임 불일치로 치명 버그가 숨어든다. 원인 해결 > 임시 플래그가 원칙.

Q. lockfile 지우는 게 항상 정답인가요?

  • A. 팀/배포 환경에서 무분별 삭제는 재현성 깨짐. 문제 재현 후 PR 단위에서 신중히 갱신.

Q. React/TS 메이저 올리기 무서울 때?

  • A. changesets로 점진 업그레이드, --filter로 모노레포 선택 설치, e2e 테스트 선 구축.

Q. Yarn에서 npm 전환(또는 반대) 해도 되나요?

  • A. 가능하나 한 프로젝트에서 혼용은 금물. corepack으로 고정, 팀 컨벤션 명확히.

7. 한 장 요약 (체크리스트)

  1. Node 버전 맞췄나? (nvm use)
  2. Lockfile/캐시 초기화 했나?
  3. peer 요구 패키지를 루트에 정확 버전으로 설치했나?
  4. semver 범위 충족하나? (caret/tilde 확인)
  5. npm dedupe / overrides 또는 yarn resolutions 적용했나?
  6. 임시 플래그 남용하지 않았나?
  7. 모노레포면 루트-패키지 버전 합의했나?

8. 맺음말

의존성 충돌은 도구의 문제가 아니라 계약(compatibility contract)의 문제다. 이 글의 루틴대로 원인 → 범위 조정 → 구조 정리 → 도구 보조 순으로 접근하면, npm/yarn install 지옥에서 금방 빠져나올 수 있어요. 댓글로 구체 로그를 남겨주면, 실제 트리 기준으로 overrides/resolutions 스니펫까지 맞춤 제공해 보겠습니다.