npm install 의존성 충돌 해결: ERESOLVE 오류가 날 때 해결법

··설명 약 12분·작성: 해비·블로그 소개

npm ERESOLVE 오류가 나면 peer dependency의 호환 범위를 어떻게 읽나요?

npm ERESOLVE는 패키지의 호환 버전 조건이 충돌할 때 확인할 오류입니다. 현재 버전과 peer dependency 범위를 짝지어 읽으세요. 독립 lab의 실패·정상 조합을 비교하고 --force나 파일 삭제보다 원인 수정을 먼저 합니다.

적용 조건·범위: 이 실습은 react-dom 19.3.0이 react ^19.3.0을 요구하는 조건을 사용합니다. lab 폴더에서 다음 명령으로 React 18과 React DOM 19의 충돌을 재현합니다.

본문 근거: docs.npmjs.com/cli/configuring-npm/package-json · docs.npmjs.com/cli/commands/npm-ls

독립 실습: peer 충돌과 정상 조합

기존 앱 밖의 새 연습 폴더에 아래 파일을 그대로 저장합니다. 표시한 경로는 이 연습 폴더 기준입니다. 하단 완료 ZIP을 사용할 경우 labs/npm-eresolve/ 폴더만 별도로 열어도 됩니다. 이전 TaskFlow 앱 파일은 필요하지 않습니다.

package.json 전체 코드

{
  "name": "taskflow-npm-eresolve",
  "private": true,
  "type": "module",
  "packageManager": "npm@11.16.0",
  "scripts": {
    "test": "node verify.mjs"
  },
  "dependencies": {
    "react": "19.3.0",
    "react-dom": "19.3.0"
  }
}

README.md 전체 코드

# npm ERESOLVE 실습

이 폴더에서만 npm을 사용합니다.

오류 재현: `npm install react@18.3.1 react-dom@19.3.0 --save-exact`

복구: `npm install react@19.3.0 react-dom@19.3.0 --save-exact`

검증: `npm ls react react-dom`, `npm test`

react-dom 19.3.0의 peerDependencies는 react ^19.3.0입니다. 최종 package.json은 복구된 상태입니다.

verify.mjs 전체 코드

import { createElement } from 'react';
import { renderToString } from 'react-dom/server';
import assert from 'node:assert/strict';
const html = renderToString(createElement('p', null, 'TaskFlow'));
assert.equal(html, '<p>TaskFlow</p>');
console.log('React 서버 렌더링 통과');
npm install react@18.3.1 react-dom@19.3.0 --save-exact
npm install react@19.3.0 react-dom@19.3.0 --save-exact
npm ls react react-dom
npm test

기대 결과: 첫 install은 peer dependency 충돌로 실패하는 재현 단계입니다. 두 번째 install 뒤 두 패키지가 19.3.0으로 맞고 npm test가 서버 렌더링 결과를 확인하면 복구 단계가 끝납니다. 아래 package.json에 test가 있으므로 npm init만 한 빈 폴더에서 npm test를 실행하지 않습니다.

변형 과제: npm ls react react-dom 결과에서 직접 의존성과 peer로 연결된 React가 같은 버전인지 비교하고 --force 없이 복구됐다는 근거를 적습니다.

peerDependency란

일반 dependency는 패키지가 직접 필요한 의존성을 선언합니다.

peerDependency는 “이 패키지는 특정 호스트 라이브러리와 함께 사용되어야 한다”는 호환 조건을 표현할 때 자주 사용합니다.

예를 들어 플러그인이 React의 특정 범위를 요구한다고 가정합니다.

{
  "peerDependencies": {
    "react": "^19.0.0"
  }
}

프로젝트가 이 조건과 맞지 않는 React 버전을 사용하면 설치 단계에서 충돌이 발생할 수 있습니다.

npm 공식 문서에서는 npm 7 이후 peerDependencies가 기본적으로 설치 대상에 포함되며, 서로 충돌하는 요구사항을 해석할 수 없으면 오류가 날 수 있다고 설명합니다.

오류 재현용 lab 만들기

실행 전 아래 독립 실습의 package.json·verify.mjs 전체 파일을 새 lab에 저장합니다. 이어지는 npm init -y는 초기 package.json 생성 방법을 설명하는 단계이며 제공 전체 파일을 저장했다면 생략합니다. test script는 제공 package.json에 정의되어 있습니다.

TaskFlow 메인 프로젝트와 분리합니다.

labs/
└─ npm-eresolve/

이 폴더는 npm만 사용합니다.

cd labs/npm-eresolve
npm init -y

이 실습은 react-dom 19.3.0이 react ^19.3.0을 요구하는 조건을 사용합니다. lab 폴더에서 다음 명령으로 React 18과 React DOM 19의 충돌을 재현합니다.

npm install react@18.3.1 react-dom@19.3.0 --save-exact

호환되는 조합으로 복구합니다.

npm install react@19.3.0 react-dom@19.3.0 --save-exact
npm ls react react-dom
npm test

아래 전체 코드와 ZIP에는 복구된 상태만 제공합니다. 오류 재현 명령은 독립 lab에서만 실행합니다.

ERESOLVE 로그 읽는 법

오류 메시지가 길어도 다음 정보부터 찾습니다.

현재 프로젝트가 사용하는 패키지
충돌한 패키지
각 패키지가 요구하는 버전 범위
peer dependency 조건
npm이 선택하지 못한 이유

핵심은 다음 문장을 만드는 것입니다.

A 패키지는 X 버전을 요구하고
B 패키지는 같은 의존성의 Y 버전을 요구해서
동시에 만족할 수 없다.

이 문장을 만들 수 있으면 해결 방향을 고르기 쉬워집니다.

package.json 먼저 확인

{
  "dependencies": {
    "react": "18.3.1",
    "react-dom": "19.3.0"
  }
}

각 패키지의 공식 문서나 npm 메타데이터에서 호환 범위를 확인합니다.

단순히 “최신 버전으로 모두 올리기”가 항상 정답은 아닙니다.

설치 트리 확인

설치 가능한 상태라면 다음 명령으로 트리를 확인할 수 있습니다.

npm ls

특정 패키지만 보고 싶다면:

npm ls react

어떤 버전이 어디에서 요구되는지 확인하는 데 도움이 됩니다.

해결 방법 1: 호환되는 버전으로 맞추기

가장 우선할 방법입니다.

호스트 라이브러리 버전 확인
→ 플러그인의 peer dependency 확인
→ 서로 만족하는 조합 선택
→ package.json 수정
→ 다시 설치

설치가 성공한 뒤 실제 앱 실행과 테스트까지 확인합니다.

해결 방법 2: 패키지 교체 또는 제거

더 이상 유지되지 않는 플러그인이 최신 호스트 라이브러리와 호환되지 않을 수 있습니다.

이 경우 무리하게 peer dependency 검사를 우회하기보다 대체 패키지를 찾거나 기능 자체를 제거하는 편이 안전할 수 있습니다.

--legacy-peer-deps는 언제 쓰는가

npm install --legacy-peer-deps

이 옵션은 peer dependency 충돌을 엄격하게 해결하지 않고 설치를 진행할 수 있게 합니다.

즉 “충돌이 해결됐다”가 아니라 “검사를 우회해서 설치했다”에 가깝습니다.

임시 확인에는 쓸 수 있지만 팀 프로젝트의 기본 해결책으로 두지 않습니다.

--force는 더 조심하기

npm install --force

강제로 진행할 수 있지만 호환되지 않는 조합이 설치될 수 있습니다.

설치 성공과 실행 정상은 다른 문제입니다.

따라서 오류 원인을 이해하지 않은 상태에서 첫 명령으로 사용하지 않습니다.

lockfile과 node_modules 삭제는 언제 하는가

package.json의 의존성 조합을 수정한 뒤 기존 설치 결과가 남아 문제를 일으키는 경우 정리가 필요할 수 있습니다.

하지만 순서는 다음과 같습니다.

충돌 원인 확인
→ 버전 조합 수정
→ lockfile 영향 판단
→ 필요한 경우 설치 상태 정리
→ 재설치
→ 실행 검증

삭제부터 시작하면 원래 충돌 정보를 잃고 같은 문제가 반복될 수 있습니다.

Windows에서 로그 확인

PowerShell에서도 npm 명령은 동일합니다.

npm install
npm ls

오류 내용을 파일로 보관할 때는 민감 정보가 포함됐는지 확인합니다.

자주 발생하는 잘못된 접근

ERESOLVE가 나오면 npm 캐시부터 지웁니다

의존성 버전 충돌은 캐시 문제가 아닐 수 있습니다.

먼저 충돌 조건을 읽습니다.

무조건 최신 버전으로 올립니다

메이저 버전 차이는 API 변경을 포함할 수 있습니다.

호환 범위를 먼저 확인합니다.

설치만 성공하면 끝났다고 판단합니다

다음 단계까지 확인합니다.

install
→ dev
→ lint
→ 필요한 테스트

TaskFlow 루트에서 npm install을 실행합니다

TaskFlow 메인 프로젝트는 pnpm 기준입니다.

npm ERESOLVE 실습은 labs/npm-eresolve에서만 진행합니다.

학습 완료 체크리스트

  • ERESOLVE가 의존성 해석 실패라는 것을 이해했습니다.
  • peerDependency의 역할을 설명할 수 있습니다.
  • 오류 로그에서 충돌한 두 조건을 찾을 수 있습니다.
  • npm ls로 설치 트리를 확인할 수 있습니다.
  • --legacy-peer-deps와 --force를 기본 해결책으로 사용하지 않습니다.
  • 호환 가능한 버전 조합을 먼저 찾습니다.
  • npm 오류 실습과 TaskFlow 메인 pnpm 환경을 분리합니다.

선택 실습: 같은 프로젝트로 연습하기

TaskFlow는 작업을 관리하는 예제 앱입니다. 이 선택 실습에서는 앱을 실행·개발하는 환경과 설정을 준비합니다.

이번 글에서는 TaskFlow 메인 프로젝트가 아니라 별도 npm lab에서 peer dependency 충돌을 재현하고, 오류 메시지에서 원인 패키지와 요구 버전을 찾는 방법을 익힙니다.

선택: 연재 프로젝트와 전체 자료

연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.

TaskFlow 프로젝트 적용

시작 체크포인트: 이전 글의 적용·완료 확인과 이전 회차 시작 ZIP을 기준으로 아래 변경 파일을 수정합니다. 메인 앱은 루트에서 실행하고, labs는 해당 폴더의 package.json과 잠금 파일을 기준으로 따로 실행합니다.

이전 완료본과 달라진 파일: docs/development-environment.md, README.md, labs/npm-eresolve/package.json, labs/npm-eresolve/README.md, labs/npm-eresolve/verify.mjs. 전체 코드에서 같은 경로를 찾아 기존 내용을 대조합니다. ZIP의 자동 생성 lockfile도 해당 패키지 폴더와 함께 유지합니다.

이번 글에서는 TaskFlow 메인 프로젝트가 아니라 별도 npm lab에서 peer dependency 충돌을 재현하고, 오류 메시지에서 원인 패키지와 요구 버전을 찾는 방법을 익힙니다.

다음 내용을 알고 시작합니다.

package.json
dependencies
devDependencies
lockfile
semver 기본

이 글을 읽고 바로 확인할 것

독자 질문: npm install에서 ERESOLVE가 나오면 충돌한 버전을 어떻게 찾고 복구하나요?

얻을 결과: 오류 로그의 peer dependency 범위를 읽고, 별도 lab에서 호환되는 버전으로 복구할 수 있습니다.

직접 따라 할 순서

  1. TaskFlow 루트와 분리된 npm lab의 package.json과 오류 로그를 확인합니다.
  2. npm ls로 설치 트리를 확인하고 요구하는 peer 버전과 현재 버전을 대조합니다.
  3. 호환되는 조합으로 수정한 뒤 npm install과 npm test를 실행합니다.

확인 결과 기준: 동일한 충돌이 재현되고 수정 후 설치·테스트가 통과하면 해결입니다. --force나 --legacy-peer-deps로 오류를 숨기는 결과만으로 완료로 보지 않습니다.

편집 검증 범위: 이번 보강에서 위 명령과 화면은 다시 실행하지 않았습니다(미확인). 본문 아래의 기존 실행 검증표는 이전 작업에서 기록한 결과이며, 독자는 자신의 환경에서 위 기준으로 확인해 주세요.

다음 학습: pnpm install 오류 해결: lockfile·workspace 점검 순서

이전 체크포인트

dev-env-step-05-eslint-flat

이번 변경

메인 TaskFlow 의존성은 변경하지 않습니다.

labs/npm-eresolve 생성
ERESOLVE 재현 절차 문서화
peer dependency 확인 순서 추가
강제 설치 옵션 사용 기준 추가

변경 파일

labs/npm-eresolve/package.json
docs/development-environment.md

검증

lab에서 충돌을 재현한 뒤 호환되는 조합으로 package.json을 수정합니다.

npm install
npm ls

최종 체크포인트에는 오류가 남아 있는 상태가 아니라 설치가 정상화된 상태를 저장합니다.

완료 체크포인트

dev-env-step-06-npm-eresolve-fixed
전체 파일·코드 보기

실습 프로젝트 전체 코드

아래는 이번 회차 완료 시점의 전체 소스입니다. 파일 경로는 압축을 푼 체크포인트 폴더를 기준으로 합니다. 자동 생성되는 lockfile은 ZIP에 포함하며, node_modules·빌드 결과·실제 비밀값은 포함하지 않습니다. 메인 앱과 각 lab은 별도의 설치 범위를 유지합니다.

.gitignore

node_modules/
dist/
storybook-static/
.env.local
.env.*.local

apps/web/.env.example

VITE_API_URL=https://api.example.invalid
VITE_APP_NAME=TaskFlow

apps/web/eslint.config.js

import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import globals from 'globals';

export default defineConfig([
  { ignores: ['dist/**'] },
  js.configs.recommended,
  {
    files: ['src/**/*.js'],
    languageOptions: { globals: globals.browser },
    rules: { 'no-console': 'warn' },
  },
]);

apps/web/index.html

<!doctype html>
<html lang="ko">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>TaskFlow 개발환경</title></head>
<body><main id="app"></main><script type="module" src="/src/main.js"></script></body>
</html>

apps/web/package.json

{
  "name": "@taskflow/web",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite --host 127.0.0.1",
    "build": "vite build",
    "lint": "eslint ."
  },
  "devDependencies": {
    "vite": "8.3.0",
    "eslint": "10.11.0",
    "@eslint/js": "10.0.1",
    "globals": "17.12.0"
  }
}

apps/web/src/config/env.js

const apiUrl = import.meta.env.VITE_API_URL;
const appName = import.meta.env.VITE_APP_NAME;

if (!apiUrl) {
  throw new Error('VITE_API_URL 환경 변수가 필요합니다.');
}

export const env = {
  apiUrl,
  appName: appName || 'TaskFlow',
};

apps/web/src/main.js

import './style.css';
import { env } from './config/env.js';

const app = document.querySelector('#app');
const title = document.createElement('h1');
title.textContent = env.appName;
const status = document.createElement('p');
status.textContent = '개발환경 스타터가 실행 중입니다.';
const api = document.createElement('p');
api.id = 'api-url';
api.textContent = `API URL: ${env.apiUrl}`;
app.append(title, status, api);

apps/web/src/style.css

body { margin: 0; color: #202020; background: #f6f6f6; font: 16px/1.6 system-ui, sans-serif; }
main { max-width: 720px; margin: 64px auto; padding: 24px; }
h1 { font-size: 30px; font-weight: 500; }
p { overflow-wrap: anywhere; }

docs/development-environment.md

# TaskFlow 개발환경

이 스타터는 개발환경 진단을 위한 최소 Vite/vanilla JavaScript 앱입니다. 할 일 관리 기능이나 API 서버는 포함하지 않습니다.

Node.js 24.19.0, pnpm 11.19.0으로 검증했습니다. 루트는 pnpm만 사용하며 apps/*만 workspace입니다. labs의 npm/Yarn/pnpm 실습은 독립 프로젝트입니다.

## 2회차

메인 앱은 pnpm을 유지합니다. labs/yarn-modern은 독립 Yarn 실습입니다.

## 3회차

apps/web/.env.example을 apps/web/.env.local로 복사합니다. .invalid 주소는 표시만 하며 실제 API 호출은 하지 않습니다. 공개 샘플값만 제공하고 로컬 파일은 ZIP에서 제외합니다.

## 4회차

Vite 루트는 apps/web입니다. env.js가 필수 VITE_API_URL을 검사하고 화면에 공개 URL을 표시합니다.

## 5회차

ESLint 설정과 의존성은 apps/web에 있습니다. 루트 pnpm lint가 앱의 eslint .을 호출합니다.

## 6회차

npm ERESOLVE 실습은 labs/npm-eresolve에서 실행합니다. 최종본은 react와 react-dom 19.3.0 호환 조합입니다.

package.json

{
  "name": "taskflow",
  "private": true,
  "packageManager": "pnpm@11.19.0",
  "engines": {
    "node": ">=24"
  },
  "scripts": {
    "dev": "pnpm --filter @taskflow/web dev",
    "build": "pnpm --filter @taskflow/web build",
    "lint": "pnpm --filter @taskflow/web lint"
  }
}

pnpm-workspace.yaml

packages:
  - "apps/*"

README.md

# TaskFlow 개발환경 06 완료 체크포인트

Node.js 24.19.0 / pnpm 11.19.0 검증 기준입니다. pnpm을 별도로 설치한 환경에서 실행하세요. ZIP에는 node_modules, dist, 로컬 환경 변수, 실제 비밀값이 없습니다.

1. 이 폴더에서 `pnpm install --frozen-lockfile`
2. PowerShell: `Copy-Item apps/web/.env.example apps/web/.env.local` 또는 macOS/Linux: `cp apps/web/.env.example apps/web/.env.local`
3. `pnpm dev` 후 http://127.0.0.1:5173 확인
4. `pnpm build`
5. `pnpm lint`

apps/web가 Vite 루트입니다. 환경 변수도 이 폴더에 둡니다. 표시용 API 주소에 실제 요청을 보내지 않습니다. labs는 메인 workspace에서 제외되어 각 폴더에서 별도 설치합니다.

원문 단계별 전체 파일과 실행 검증 범위는 docs/development-environment.md를 확인하세요.

labs/yarn-modern/.yarnrc.yml

nodeLinker: node-modules

labs/yarn-modern/package.json

{
  "name": "taskflow-yarn-lab",
  "private": true,
  "type": "module",
  "packageManager": "yarn@4.18.0",
  "scripts": {
    "dev": "node src/main.js",
    "lint": "node --check src/main.js"
  }
}

labs/yarn-modern/src/main.js

console.log('TaskFlow Yarn 독립 실습');

labs/npm-eresolve/package.json

{
  "name": "taskflow-npm-eresolve",
  "private": true,
  "type": "module",
  "packageManager": "npm@11.16.0",
  "scripts": {
    "test": "node verify.mjs"
  },
  "dependencies": {
    "react": "19.3.0",
    "react-dom": "19.3.0"
  }
}

labs/npm-eresolve/README.md

# npm ERESOLVE 실습

이 폴더에서만 npm을 사용합니다.

오류 재현: `npm install react@18.3.1 react-dom@19.3.0 --save-exact`

복구: `npm install react@19.3.0 react-dom@19.3.0 --save-exact`

검증: `npm ls react react-dom`, `npm test`

react-dom 19.3.0의 peerDependencies는 react ^19.3.0입니다. 최종 package.json은 복구된 상태입니다.

labs/npm-eresolve/verify.mjs

import { createElement } from 'react';
import { renderToString } from 'react-dom/server';
import assert from 'node:assert/strict';
const html = renderToString(createElement('p', null, 'TaskFlow'));
assert.equal(html, '<p>TaskFlow</p>');
console.log('React 서버 렌더링 통과');

실행 검증 결과

검증 환경: Windows · Node.js 24.19.0 · pnpm 11.19.0 · Vite 8.3.0 · ESLint 10.11.0.

항목 확인 결과
메인 앱 pnpm install --frozen-lockfile 성공
메인 앱 pnpm build 성공
메인 앱 pnpm lint 성공
npm lab의 ERESOLVE 호환되지 않는 React 조합에서 예상 실패 재현
npm lab의 복구 install · npm ls · npm test 성공 · React 서버 렌더링 결과 확인

메인 앱의 개발 서버 응답과 환경 변수 변환은 1·4·5·11회차에서 대표 확인했습니다. 선택 lab과 에디터 기능은 위에 표시한 범위만 검증했으며, 운영 배포 검증은 포함하지 않습니다.

다음 학습

다음 글에서는 TaskFlow가 실제로 사용하는 pnpm으로 돌아옵니다.

이번에는 peer dependency 한 가지가 아니라 workspace 루트, pnpm-workspace.yaml, lockfile, packageManager를 함께 확인하며 pnpm install 오류를 복구합니다.

다음 글: pnpm install 오류 해결: lockfile·workspace 점검 순서

공식 문서

체크포인트 다운로드

dev-env-step-06-npm-eresolve-fixed 완료 ZIP 다운로드

이전 회차 시작 체크포인트 ZIP

글 소개·학습 준비·전체 목차
npm 의존성 충돌 해결을 표현한 패키지 상자와 곰 표지

개발환경 · 6 / 11 · 오류 해결 · npm ERESOLVE

npm install을 실행했는데 ERESOLVE가 나오면 가장 먼저 해야 할 일은 node_modules를 지우는 것이 아니라 “어떤 패키지 두 개가 서로 다른 버전을 요구하는지” 읽는 것입니다.

이번 글에서 배우는 내용

  • dependency와 peerDependency 차이
  • npm ERESOLVE가 발생하는 이유
  • 오류 로그에서 충돌 패키지 읽기
  • npm ls로 설치 트리 확인
  • package.json 버전 범위 비교
  • --force와 --legacy-peer-deps를 바로 쓰면 안 되는 이유
  • 정상적인 해결 방법 선택 기준
  • TaskFlow 메인 pnpm 환경과 npm 오류 lab 분리

선수 지식

package.json·의존성 버전 범위·lockfile을 이해하고 Node.js와 npm이 준비되어 있으면 됩니다. 실습은 아래 독립 lab 안에서 진행합니다.

목차

  1. peerDependency란
  2. 오류 재현용 lab 만들기
  3. ERESOLVE 로그 읽는 법
  4. package.json 먼저 확인
  5. 설치 트리 확인
  6. 해결 방법 1: 호환되는 버전으로 맞추기
  7. 해결 방법 2: 패키지 교체 또는 제거
  8. --legacy-peer-deps는 언제 쓰는가
  9. --force는 더 조심하기
  10. lockfile과 node_modules 삭제는 언제 하는가
  11. Windows에서 로그 확인
  12. 자주 발생하는 잘못된 접근
  13. 학습 완료 체크리스트
  14. 독립 실습: peer 충돌과 정상 조합
  15. TaskFlow 프로젝트 적용
  16. 이 글을 읽고 바로 확인할 것
  17. 실습 프로젝트 전체 코드
  18. 실행 검증 결과
  19. 다음 학습
  20. 공식 문서

이 글이 도움이 되었나요?

조회 중

읽음 기록

읽음 — / 전체 11개 · 필수 6개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기