Skip to content

fix(build): change output extension from .mjs to .js for Next.js 13 compatibility#83

Merged
ywkim merged 1 commit into
mainfrom
fix/nextjs13-mjs-compatibility
Jan 4, 2026
Merged

fix(build): change output extension from .mjs to .js for Next.js 13 compatibility#83
ywkim merged 1 commit into
mainfrom
fix/nextjs13-mjs-compatibility

Conversation

@ywkim

@ywkim ywkim commented Jan 4, 2026

Copy link
Copy Markdown
Contributor

요약

Next.js 13.x 버전의 webpack CJS/ESM interop 버그로 인해 .mjs 빌드 파일에서 next/image import가 실패하는 문제를 .js 확장자로 회피.

목적

버그 성격: Next.js 13.5.6~14.0.4의 webpack 버그로, .mjs 파일을 강제로 순수 ESM으로 처리하여 CJS 모듈(next/image)과의 interop이 실패함. 14.1.0에서 수정되었으나, 하위 호환성을 위해 .js 확장자를 사용.

기술적 배경:

  • .mjs.js + "type": "module" 모두 Node.js 표준 ESM 방식
  • 둘 다 올바른 선택이며, .mjs가 "잘못된" 것이 아님
  • Next.js 13.x의 버그로 인해 .mjs만 작동하지 않았음

주요 개선 사항

🔧 빌드 설정 변경

  • Vite 빌드 출력: .mjs.js 확장자 (Next.js 13.x 호환성)
  • ESM-only 빌드: CJS 빌드 제거, "type": "module"만 유지
  • package.json exports: 경로를 .js로 업데이트

✅ 호환성 개선

  • Next.js 13.5.6에서 next/image 정상 동작 확인
  • Next.js 14.0.x까지 호환성 확보
  • webpack CJS/ESM interop 버그 회피

📦 package.json 변경

- "module": "dist/index.mjs",
+ "module": "dist/index.js",
  "exports": {
    ".": {
-     "import": "./dist/index.mjs",
-     "require": "./dist/index.js"
+     "import": "./dist/index.js"
    }
  }

⚙️ vite.config.ts 변경

lib: {
-  formats: ["es", "cjs"],
-  fileName: (format, entryName) =>
-    `${entryName}.${format === "es" ? "mjs" : "js"}`,
+  formats: ["es"],
+  fileName: () => "[name].js",
}

Next.js 버전별 호환성 검증

테스트 결과

Next.js 버전 .mjs 빌드 .js 빌드 비고
13.5.6 ❌ Element type is invalid ✅ 정상 동작 webpack 버그
13.5.7 ❌ Element type is invalid ✅ 정상 동작 webpack 버그
14.0.0 ❌ Element type is invalid ✅ 정상 동작 webpack 버그
14.0.4 ❌ Element type is invalid ✅ 정상 동작 webpack 버그
14.1.0+ ✅ 수정됨 ✅ 정상 동작 버그 수정

Next.js 14.1.0 버그 수정사항

해결 PR: vercel/next.js#60169 - Fix emitting ESM swc helpers for 3rd parties CJS libs in bundle

버그 원인:

  • PR #58967에서 모든 모듈을 type: 'es6'로 강제 설정
  • CJS 외부 라이브러리(next/image 등)도 ESM으로 잘못 처리
  • swc가 modern syntax 컴파일 시 ESM 스타일 helpers를 emit → CJS 호환성 깨짐

수정 방법:

  • auto-cjs swc plugin 버그 수정 (@kdy1)
  • 기본 모듈 타입 감지 복원
  • 파일이 CJS인지 ESM인지 정확히 판별하여 올바른 helpers emit

핵심 변경 (packages/next/src/build/swc/options.ts):

// BEFORE (14.0.4): 강제로 es6 모듈 타입 지정
function getModuleOptions(esm: boolean | undefined = false) {
  return esm ? { module: { type: 'es6' } } : {}
}

// AFTER (14.1.0): 파일별로 적절한 모듈 타입 감지
function shouldOutputCommonJs(filename: string) {
  return isCommonJSFile(filename) || nextDistPath.test(filename)
}
const moduleResolutionConfig = shouldOutputCommonJs(filename)
  ? { module: { type: 'commonjs' } }
  : {}  // 기본값 사용 (auto-cjs 플러그인이 자동 감지)

결론:

  • Next.js 14.1.0+에서는 .mjs 파일도 정상 동작
  • .js 선택은 Next.js 13.x~14.0.x 하위 호환성을 위한 실용적 결정
  • .mjs.js 모두 표준이며, 둘 중 하나가 더 "올바른" 것은 아님

구조 변경

변경된 파일:

frontend/
├── .gitignore         # *.tgz 추가
├── package.json       # exports 경로 .js로 변경
├── package-lock.json  # peer dependency 정리
└── vite.config.ts     # ESM-only .js 빌드

테스트 체크리스트

  • Next.js 13.5.6 환경에서 빌드 성공
  • next/image 컴포넌트 정상 렌더링
  • URL 미리보기 카드 표시 확인
  • "Element type is invalid" 에러 없음
  • Next.js 14.1.0에서 버그 수정 확인
  • 릴리스 노트 및 관련 PR 검증
  • Node.js ESM 표준 확인 (.mjs와 .js 모두 유효)

개선 효과

기존 (.mjs + Next.js 13.x ~ 14.0.x):

  • Next.js webpack 버그로 .mjs를 강제 순수 ESM 처리
  • CJS 모듈(next/image)과의 interop 실패
  • import 시 빈 객체 반환 → 렌더링 에러

개선 후 (.js + "type": "module"):

  • .js + "type": "module"로 ESM 명시 (표준 방식)
  • webpack이 CJS interop 정상 처리
  • Next.js 13.x ~ 14.x 모두 호환

🤖 Generated with Claude Code

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 Next.js 13과의 호환성을 위해 빌드 결과물의 확장자를 .mjs에서 .js로 변경하고 ESM 전용 패키지로 전환하는 것을 목표로 하고 있습니다. 전반적인 변경 방향은 좋지만, 빌드 실패나 런타임 오류를 유발할 수 있는 두 가지 치명적인 문제를 발견했습니다. 첫째, package.json"type": "module" 필드가 누락되어 Node.js가 .js 파일을 ESM으로 인식하지 못할 수 있습니다. 둘째, vite.config.tsfileName 함수가 잘못 설정되어 빌드 시 파일 이름이 충돌할 가능성이 높습니다. 각 문제에 대한 수정 제안을 코드 리뷰 댓글로 남겼으니 확인 부탁드립니다.

Comment thread frontend/package.json
Comment thread frontend/vite.config.ts
…ompatibility

문제:
- Next.js 13.5.6에서 .mjs 파일이 next/image를 import할 때 "Element type is invalid" 에러 발생
- webpack CJS/ESM interop 실패

해결:
- Vite 빌드 출력을 .js 확장자로 변경 (ESM-only, "type": "module")
- package.json exports 경로 업데이트

검증:
- Next.js 13.5.6 환경에서 next/image 정상 동작 확인

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@ywkim ywkim force-pushed the fix/nextjs13-mjs-compatibility branch from 9faee7d to eefcda4 Compare January 4, 2026 07:00
@ywkim ywkim merged commit b4adc1d into main Jan 4, 2026
4 checks passed
@ywkim ywkim deleted the fix/nextjs13-mjs-compatibility branch January 4, 2026 07:34
@github-actions

github-actions Bot commented Jan 4, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.14.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

ywkim added a commit that referenced this pull request Jan 4, 2026
#84)

## 요약

**Vite 빌드 설정을 dual ESM/CJS 패턴으로 복원**하여 Next.js 13과 Jest 모두 호환되도록 수정했습니다.

## 목적

PR #22 이후 PR #83으로 ESM-only로 회귀했으나, 이는 다음 두 가지 문제를 야기합니다:
- ❌ Next.js 13.5.6 `.mjs` 버그: ESM 콘텐츠를 CJS 컨텍스트에서 로드할 때 webpack 오류 발생
- ❌ Jest 호환성: CJS 기반 도구에서 `transformIgnorePatterns` 설정 필수

## 주요 개선 사항

### 📦 Vite 설정 변경

**Before (PR #83 - ESM-only)**:
```typescript
formats: ["es"]
fileName: () => "[name].js"
// 결과: index.js, locale.js (ESM만)
```

**After (PR #22 패턴 복원)**:
```typescript
formats: ["es", "cjs"]
fileName: (format, entryName) => `${entryName}.${format === "es" ? "js" : "cjs"}`
// 결과: index.js + index.cjs, locale.js + locale.cjs
```

### 📄 package.json 동기화

**main 필드**: `dist/index.js` → `dist/index.cjs`
- CommonJS fallback (exports 미지원 환경 대응)

**module 필드**: `dist/index.mjs` → `dist/index.js`
- ESM, 번들러가 우선 사용

**exports 필드**: `require` 필드 추가
```json
{
  "import": "./dist/index.js",      // ESM
  "require": "./dist/index.cjs"     // CJS
}
```

### 🔄 aioia-core v0.2.2와 일관성

aioia-core PR #23에서 도입한 dual-build 패턴을 trade-safety에도 동일하게 적용:
- ✅ 표준 npm 라이브러리 방식
- ✅ 모든 ESM-first 소비자와 호환

## 검증

- ✅ `npm run build:lib` 성공 (ESM/CJS 모두 생성됨)
- ✅ `npm pack` 검증 (모든 파일 포함 확인)
- ✅ 로컬 설치 성공
- ✅ `tsc --noEmit` (type-check) 통과
- ✅ Next.js dev 서버 정상 실행
- ✅ 브라우저 UI 렌더링 정상

## 기술 배경

### 왜 .js (not .mjs)?
- `.mjs` → Next.js 13.5.6 webpack이 강제 `es6` 모듈 타입 적용 → CJS interop 실패
- `.js` + `"type": "module"` → webpack이 자동 감지 → CJS interop 정상 작동
- Next.js 14.1.0+에서 이 버그 수정됨 (PR #60169)

### 왜 CJS도 필요?
- Jest/CJS 도구 호환: 별도 설정 없이 "그냥 작동"
- `transformIgnorePatterns` 설정 불필요
- 표준 npm 라이브러리 패턴 (업계 표준)

## 테스트 체크리스트

- [x] 빌드 성공 (vite build)
- [x] ESM 출력 생성 (dist/index.js, dist/locale.js)
- [x] CJS 출력 생성 (dist/index.cjs, dist/locale.cjs)
- [x] npm pack 검증
- [x] 로컬 설치 및 검증 성공
- [x] TypeScript type-check 통과
- [x] Next.js dev 서버 실행 성공
- [x] 페이지 렌더링 확인

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant