@maru

Node.js 24 require(esm) 안정화 — NestJS 12의 순수 ESM 전환이 가능했던 이유
자바스크립트 백엔드 생태계의 고질적인 골칫거리였던 ERR_REQUIRE_ESM 에러가 마침내 역사 속으로 사라지고 있습니다. Node.js 24에서 CommonJS 환경이 ESM 패키지를 동기식으로 직접 불러올 수 있는 require(esm) 기능이 안정화되었기 때문입니다. 덕분에 복잡한 번들러 설정이나 이중 빌드 같은 우회책 없이도 최신 ESM 패키지를 자연스럽게 도입할 수 있는 길이 열렸습니다. 백엔드 생태계의 모듈 통합을 가속화하는 이 변화의 핵심과 실무적인 의의를 정리합니다.
Node.js 24의 require(esm) 작동 원리와 핵심 제약
그동안 자바스크립트 개발자를 괴롭히던 ERR_REQUIRE_ESM 에러가 Node.js 24에서 마침내 완전히 해결되었습니다. 핵심 기여자 조이 청의 주도로 완성된 이 메커니즘은 CommonJS 환경에서 ESM 패키지를 동기식으로 직접 불러올 수 있게 지원합니다.
동작 원리는 V8 엔진 수준에서 ESM 모듈 그래프를 동기적으로 파싱하고 분석하는 데 있습니다. 본래 ECMAScript 명세상 탑레벨 await가 없는 ESM 그래프는 동기 평가가 가능합니다. Node.js 런타임은 이 점을 활용해 모듈을 동기식으로 컴파일한 뒤 결과를 즉시 반환합니다.
덕분에 기존 CommonJS 프로젝트에서도 빌드 도구 체인을 복잡하게 수정하지 않고 순수 ESM 라이브러리를 곧바로 호출할 수 있습니다.
// app.cjs (Node.js 24+ 환경)
const { someEsmFunction } = require('esm-only-package');
// 별도의 비동기 래핑 없이 즉시 동기 실행 가능
const result = someEsmFunction();
console.log(result);다만 이 메커니즘은 가져오려는 ESM 모듈 그래프 내부에 탑레벨 await가 존재하지 않아야 한다는 명확한 제약이 있습니다. 탑레벨 await가 포함되면 평가 과정이 비동기화되어 프로미스를 반환하므로, 동기 함수인 require로 호출 시 ERR_REQUIRE_ASYNC_MODULE 에러가 발생합니다.
다행히 대다수 npm 라이브러리는 하위 호환성을 위해 탑레벨 await를 사용하지 않으므로 실무 수준에서는 아무런 제약 없이 결합해 사용할 수 있습니다.
NestJS 12의 ESM 전환이 고통스럽지 않은 이유
NestJS 12 로드맵의 가장 큰 변화는 모든 코어 패키지를 순수 ESM으로 전환한다는 점입니다. 과거였다면 이 결정은 기존 CommonJS 기반 애플리케이션의 하위 호환성을 깨뜨리는 치명적인 파열음을 냈을 것입니다. 하지만 Node.js 24가 제공하는 동기식 require(esm) 덕분에, 기존 프로젝트의 코드를 전혀 수정하지 않고도 최신 순수 ESM 패키지를 불러와 사용할 수 있게 되었습니다.
InfoQ의 로드맵 보고서와 byteiota 분석에 따르면, NestJS 창시자인 카밀 미실리비에크는 Node.js의 require(esm) 지원이 'ESM 전환을 실질적으로 가능하게 만든 마지막 퍼즐 조각'이었으며, 이 기능이 없었다면 이번 마이그레이션은 큰 의미가 없었을 것이라고 강조했습니다. 호환성 장벽이 런타임 수준에서 해결되면서, 프레임워크 코어 팀이 안심하고 전면적인 체질 개선에 나설 수 있게 된 셈입니다.
덕분에 NestJS 12는 기존 비즈니스 로직에 영향을 주지 않으면서도, 오랜 컴파일러 부채와 모듈 설정의 복잡성을 완전히 덜어내는 모범 사례를 보여주었습니다. 개발자들은 당장 프로젝트의 구조를 바꾸지 않아도 ESM 전용 라이브러리를 자유롭게 결합할 수 있게 되어, 모듈 시스템 간의 오랜 갈등에서 마침내 해방되었습니다.
듀얼 패키지 허들의 종말과 빌드 파이프라인 단순화
그동안 오픈소스 라이브러리 메인테이너들은 CommonJS와 ESM 환경을 모두 지원하기 위해 두 포맷의 빌드 아티팩트를 동시에 제공해 왔습니다. 이 과정에서 애플리케이션이 동일한 패키지를 서로 다른 모듈 시스템으로 각각 불러올 때 발생하는 '이중 패키지 위험(Dual Package Hazard)'이 고질적인 문제였습니다. 메모리상에서 싱글톤 객체가 중복으로 생성되면서 전역 상태가 깨지거나 데이터베이스 커넥션이 이중으로 수립되는 등 추적하기 극도로 어려운 런타임 버그가 빈번하게 일어났습니다.
Node.js 24의 require(esm) 안정화는 이 문제를 해결하는 핵심 열쇠입니다. 라이브러리를 순수 ESM 포맷 하나로만 배포하더라도 CommonJS 백엔드가 이를 직접 동기식으로 호출할 수 있기 때문입니다. 단 하나의 모듈 인스턴스로만 평가되고 로드되므로, 싱글톤 객체가 중복 생성되어 발생하는 버그가 원천 차단됩니다.
결과적으로 복잡했던 빌드 파이프라인과 tsconfig.json 설정이 획기적으로 단순해집니다. 메인테이너는 번들러에서 까다로운 조건부 내보내기 설정을 거치고 CJS와 ESM 버전을 이중 컴파일하던 낭비를 끝낼 수 있습니다. 엔터프라이즈 개발팀 또한 더 이상 하이브리드 빌드와 번들링 스트레스에 시달리지 않고 순수 ESM 라이브러리를 프로덕션에 자유롭게 도입할 수 있게 되었습니다.
2026년 백엔드 마이그레이션을 위한 실무 체크리스트
순수 ESM 백엔드로 안전하게 전환하려면 먼저 런타임 환경을 require(esm) 기능이 안정화된 Node.js 24 버전이나 백포트가 완료된 Node.js 22.12.0+, 20.19.0+ 이상의 LTS 버전으로 마이그레이션해야 합니다.
그 다음 TypeScript 설정 파일의 module과 moduleResolution 옵션을 NodeNext로 변경하고, 모든 로컬 파일 임포트 경로 끝에 .js 확장자를 명시하는 규칙을 적용합니다. ESM 환경에서 사용할 수 없는 __dirname이나 __filename 변수는 import.meta.url을 활용해 대체하도록 코드를 가볍게 수정해 줍니다.
복잡한 이중 빌드 설정을 완전히 걷어내고 컴파일러 도구 체인을 단순화할 수 있는 지금이 백엔드 모듈 아키텍처를 현대화할 가장 좋은 기회입니다.
참고 링크
- Node.js Official Changelogs / Joyee Cheung — require(esm) Reaches Full Stability in Node.js v24.15.0 LTS and v25.4.0
- TypeScript Official TSConfig Reference — TypeScript TSConfig Recommendations for Modern Node ESM
- Node.js Official Documentation — The Dual-Package Hazard Solution: 'module-sync' Export Condition