TechFeedTechFeed
개발자 작업환경

ERR_REQUIRE_ESM, require, ES module | 패키지를 불러오다 막히면?

ERR_REQUIRE_ESM은 파일이 없어서가 아니라 CommonJS require가 ES 모듈만 있는 패키지를 열려고 해서 납니다. type 필드, import 전환, 로컬과 CI 노드 버전. Node.js, npm, 한국 1인 넥스트 기준. 2026년 9월 Node.js ERR_REQUIRE_ESM 문서.

by

ERR_REQUIRE_ESM은 파일이 없어서가 아니라, CommonJS의 require가 ES 모듈만 있는 패키지를 열려고 해서 납니다. 지워 다시 깔아도 같은 줄이면 package.jsontype과 불러오기 방식부터 맞추세요.


넥스트 프로젝트에 라이브러리 하나를 넣었는데 개발 서버가 바로 빨간 줄로 죽는 일, 1인으로 사이트를 돌리다 보면 만납니다. 맥에선 되고 깃허브 액션만 터지거나, 노드를 올렸더니 어제까지 되던 스크립트가 같은 이름을 못 여는 경우도 흔합니다.


환경변수로 우회하기 전에 그 패키지가 CJS인지 ESM인지, 지금 파일이 require인지 import인지부터 가르면 대부분 끝납니다. 근거는 Node.js ERR_REQUIRE_ESM패키지 type 문서에 있습니다.


require가 ESM을 열 때 나는 줄인가

파일이 없는 게 아니라 모듈 형식이 다른 줄입니다. 노드는 파일을 열 때 CommonJS와 ES 모듈을 다른 로더로 다룹니다. require()는 기본적으로 CJS 로더이고, import는 ESM 로더입니다. 대상 패키지가 "type": "module"이거나 진입점이 .mjs면, 구버전 노드의 require는 그 파일을 열지 못하고 ERR_REQUIRE_ESM을 던집니다.


Cannot find module과 자리가 다릅니다. 그쪽은 이름을 node_modules 사다리에서 못 찾은 줄이고, 이 줄은 이름은 찾았는데 로더가 거부한 줄입니다. 그래서 rm -rf node_modules를 반복해도 같은 문장이 남습니다.


에러 메시지에는 보통 어떤 파일이 어떤 패키지를 불러다 막혔는지 경로가 같이 찍힙니다. 맨 아래 스택만 보지 말고, Error [ERR_REQUIRE_ESM]: require() of ES Module 바로 아래 패키지 이름부터 복사하세요.


먼저 기억할 것 | 설치가 실패한 게 아닙니다. 디스크에 패키지는 있고, 불러오는 문법과 패키지가 선언한 형식이 어긋난 겁니다. 지워 깔기 전에 그 두 칸을 나란히 보세요.


터미널에 ERR_REQUIRE_ESM이 뜨며 require가 ES 모듈을 거부하는 개념 이미지
이름은 찾았는데 CJS 로더가 ESM 파일을 거부하면 이 줄이 난다

범인을 찾는 세 칸

노드 버전, 패키지 type, 불러오기 문법 세 칸이면 원인이 갈립니다. 표로 먼저 나눕니다.


증상먼저 할 일
노드 버전로컬은 되고 CI만 실패node -v, .nvmrc, engines
패키지 type특정 라이브러리만 죽음node_modules/그이름/package.json
불러오기 문법앱은 되고 스크립트만 실패require인지 import인지
확장자.mjs만 실패진입점 main vs exports
트랜스파일ts-node, tsx만 실패실행기가 CJS로 강제하는지

노드 22와 20.19 근처부터는 require()로 ESM을 여는 길이 문서에 나와 있습니다. 다만 대상 모듈이 최상위 await를 쓰거나, CI가 아직 18을 쓰면 같은 줄이 그대로 납니다. 로컬이 22인데 액션이 18이면 설치가 아니라 런타임 버전이 원인입니다.


패키지 칸은 node_modules/패키지/package.json을 직접 엽니다. "type": "module"이 있고 exports에 CJS 조건이 없으면, 그 패키지는 import 전용입니다. 락파일 충돌과는 다른 자리입니다. 트리를 못 만드는 줄은 ERESOLVE를 보세요.


버전과 패키지 type을 같이 찍기
node -v # 에러에 찍힌 패키지 폴더를 연다 node -e "console.log(require('./node_modules/문제패키지/package.json').type)" node -e "console.log(require('./node_modules/문제패키지/package.json').exports)"

import로 바꾸거나 CJS 진입점을 쓰기

고치는 원칙은 불러오기와 대상 형식을 같게 만드는 것입니다. 내 파일이 이미 ESM이면 requireimport로 바꿉니다. 스크립트가 아직 CJS인데 새 패키지가 ESM만 주면, 스크립트를 .mjs로 바꾸거나 package.json"type": "module"을 넣고 import로 통일합니다.


당장 스크립트 전체를 못 바꾸면 동적 import()를 씁니다. import()는 Promise를 돌려서, 최상위 await가 되는 맥락이거나 async 함수 안에서 받아야 합니다. CJS 파일 한가운데에 그냥 await import를 넣으면 문법 에러가 나니, 진입점만 먼저 ESM으로 올리는 편이 덜 꼬입니다.


패키지 쪽을 내릴 수도 있습니다. 같은 라이브러리의 이전 메이저가 CJS 진입점을 남겨 둔 경우가 있습니다. 다만 보안 패치가 끊긴 버전으로 고정하는 건 임시입니다. 가능하면 내 코드를 import로 맞추는 쪽이 오래갑니다.


CJS 스크립트에서 동적 import로 우회
// scripts/foo.cjs (아직 CommonJS) async function main() { const mod = await import('esm-only-package') await mod.run() } main().catch((err) => { console.error(err) process.exit(1) })
  • [ ] 에러에 찍힌 패키지 이름을 그대로 복사했다
  • [ ] 그 패키지 package.json의 type과 exports를 봤다
  • [ ] 내 파일이 require인지 import인지 확인했다
  • [ ] node -v가 로컬과 CI에서 같은지 봤다
  • [ ] 지워 깔기 전에 불러오기 문법부터 맞췄다

require를 import로 바꾸거나 동적 import로 ESM 패키지를 여는 흐름 이미지
로더를 맞추면 설치를 반복하지 않아도 같은 줄이 사라진다

넥스트와 노드 스크립트가 갈리는 이유

앱은 되는데 node scripts/만 죽는 경우가 많습니다. 넥스트 번들러는 import를 알아서 묶고, 순수 노드 스크립트는 파일 확장자와 type을 그대로 따릅니다. 화면이 켜진다고 발행 스크립트까지 ESM을 소화하는 건 아닙니다.


tsx나 ts-node로 타입스크립트 스크립트를 돌릴 때도 갈립니다. 실행기가 내부에서 require를 쓰면, 의존성 하나가 ESM 전용인 순간 같은 줄이 납니다. 그럴 때는 실행기를 node --import tsx처럼 ESM 경로로 바꾸거나, 스크립트를 빌드한 뒤 돌리는 편이 안전합니다.


모듈 이름을 못 찾는 줄과 섞이면 진단이 늦어집니다. 이름이 디스크에 없는 줄은 Cannot find module입니다. 노드 버전 자체가 프로젝트와 다르면 버전 매니저 글을 먼저 보세요.


NODE_OPTIONS=--experimental-require-module 는 마지막 | 플래그로 열면 버전마다 동작이 갈리고, CI에 같은 플래그가 없으면 다시 죽습니다. 공식 경로는 불러오기 문법을 맞추는 쪽입니다.


실전에서 고치는 순서

순서를 정해 두면 설치를 반복하지 않습니다. 메시지에서 패키지 이름을 읽고, 그 폴더의 type을 확인하고, 내 파일이 require인지 봅니다. ESM 전용이면 import로 바꾸거나 스크립트 진입점을 ESM으로 올립니다.


그다음에야 노드 버전을 맞춥니다. 로컬 22, 액션 18처럼 어긋난 채 패키지만 올리면 같은 줄이 액션에서만 재현됩니다. engines와 버전 파일을 커밋해 두면 다음 클론에서 덜 헤맵니다.


그래도 안 열리면 그 패키지의 CJS 진입점 여부를 exports에서 확인합니다. require 조건이 있으면 버전을 그 조건이 있는 줄로 맞출 수 있습니다. 포트가 막혀 서버가 안 뜨는 줄은 EADDRINUSE라 이 에러와 동시에 보여도 원인 칸이 다릅니다.


메시지무엇을 못 하나다음
ERR_REQUIRE_ESMCJS 로더가 ESM 파일을 염이 글
Cannot find module이름을 사다리에서 못 찾음모듈 탐색 글
ERESOLVE설치 트리 피어 충돌npm ERESOLVE 글

로컬 노드와 CI 노드 버전이 달라 ERR_REQUIRE_ESM이 액션에서만 나는 개념 이미지
로컬 22와 액션 18이 갈리면 설치가 아니라 런타임 버전을 맞춘다

참고 자료


내부 연계: Cannot find module, npm ERESOLVE, 노드 버전 맞추기, 포트 점유


인용한 동작은 2026년 9월 공개 문서 기준입니다.


자주 묻는 질문

node_modules를 지워도 같은 줄이 뜹니다.

설치 문제가 아닙니다. 패키지는 있는데 require가 ESM 파일을 거부한 줄입니다. 그 패키지 package.json의 type과 내 파일의 불러오기 문법을 먼저 맞추세요.


넥스트 앱은 되는데 발행 스크립트만 죽습니다.

번들러는 import를 묶어 주고, 순수 노드는 파일 type을 그대로 따릅니다. scripts 폴더가 require를 쓰면 ESM 전용 의존성에서 같은 줄이 납니다. 진입점을 mjs로 올리거나 동적 import를 쓰세요.


로컬은 되고 깃허브 액션만 실패합니다.

노드 버전을 의심하세요. 로컬 22에서 되던 require(ESM)이 액션 18에선 거절됩니다. node -v와 버전 파일을 같게 맞춘 뒤에 패키지를 손보세요.


Cannot find module과 무엇이 다른가요?

Cannot find module은 이름을 폴더 사다리에서 못 찾은 줄입니다. ERR_REQUIRE_ESM은 이름은 찾았고 로더가 형식을 거부한 줄입니다. 설치 로그와 런타임 스택을 섞지 마세요.


패키지 버전을 내리면 해결되나요?

이전 메이저가 CJS 진입점을 남긴 경우에만 잠깐 됩니다. 보안 패치가 끊기면 다음 사고가 납니다. 가능하면 내 코드를 import로 맞추는 쪽이 안전합니다.


experimental 플래그를 켜면 끝나나요?

로컬 한 세션은 넘어갈 수 있습니다. CI와 배포에 같은 플래그가 없으면 다시 죽고, 노드 버전마다 동작이 갈립니다. 공식 경로는 문법과 type을 맞추는 것입니다.


ERR_REQUIRE_ESM은 CJS require가 ESM 파일을 열지 못했다는 뜻입니다. 패키지 type과 불러오기 문법을 맞추고, 로컬과 CI의 노드 버전을 같게 둔 뒤에야 설치를 다시 하세요. 관련 글: 모듈을 찾을 수 없음, ERESOLVE, 노드 버전.


ERR_REQUIRE_ESMrequireES moduleCJSimport노드넥스트npm작업환경개발자

함께 보면 좋은 문제 해결

EXPLORE / 개발자 작업환경

이어서 읽어보기

전체 토픽 둘러보기