코딩/문제 해결

React.lazy(Lazy Loading) 적용 후, 빌드하면 렌더링이 안 되는 문제 (esbuild target es2015 이슈)

심심해서걍함 2026. 2. 14. 21:49

증상

개발 환경(npm run dev)에서는 잘 동작하는데, 프로덕션 빌드(npm run build && npm run preview 혹은 배포) 후 흰 화면(Blank Screen) 이 뜨고 라우트가 렌더링되지 않았다.

대부분의 경우 콘솔에서 아래 중 하나를 볼 수 있다.

  • Uncaught Error: Dynamic require of "xxx" is not supported
  • Failed to fetch dynamically imported module
  • 혹은 라우트 전환 시점에만 아무것도 렌더링되지 않음

이런 유형은 “코드 스플리팅/지연 로딩 조각(chunk)”을 가져오는 과정이 깨졌을 때 자주 나타난다.

 

 

원인: React.lazy는 결국 dynamic import()를 쓴다

React.lazy(() => import('./SomePage'))는 문법만 React스럽게 감싼 형태고, 본질은 import()(dynamic import) 기반 코드 스플리팅이다. 번들러가 이 import()를 분석해서 청크를 만들고, 런타임에 필요할 때 가져온다.

 

그런데 문제는 esbuild 타깃을 es2015로 낮춰 둔 설정이었다.

왜 es2015가 문제를 만들 수 있나?

import()(dynamic import)는 ES2015 스펙에 포함된 기능이 아니라, 더 이후의 모던 ESM 환경(브라우저 모듈 + dynamic import 지원)을 전제로 한다.
esbuild는 “타깃 환경이 dynamic import를 지원하지 않는다”고 판단하면, 어떤 경우 import()를 require() 형태로 바꿔버리는 변환을 해버릴 수 있는데(특히 과거 버전에서), 이게 브라우저 번들에서는 치명적이다. 브라우저에선 require가 없으니까 런타임에 터지고, 결국 화면이 안 뜬다.

실제로 esbuild 쪽에서도 “브라우저 타깃에서 dynamic import를 require로 바꿔버려 문제가 된다”는 이슈가 보고된 바 있다.

(https://github.com/evanw/esbuild/issues/1281)

 

 

재현 예제 (Vite + React Router + React.lazy)

1) 라우트 Lazy Loading

 
// src/App.tsx
import { Suspense, lazy } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";

const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));

export default function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

2) 문제를 일으킨 빌드 설정 (예: target: "es2015")

 
// vite.config.ts import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], build: { target: "es2015", // 👈 이 설정이 문제의 트리거였던 케이스 }, });

이 상태에서 빌드하면, 개발 서버에서는 정상인데 빌드 산출물에서 lazy chunk 로딩이 깨지면서 화면이 안 뜰 수 있다.

 

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  build: {
    target: "es2015", // 👈 이 설정이 문제의 트리거였던 케이스
  },
});

 

 

이 상태에서 빌드하면, 개발 서버에서는 정상인데 빌드 산출물에서 lazy chunk 로딩이 깨지면서 화면이 안 뜰 수 있다.

 

 

 


해결 1) build.target을 ‘modules’/‘es2020’/‘esnext’로 상향

Vite의 build.target 기본값은 “모듈을 지원하는 브라우저” 묶음(modules)이고, 여기에 native dynamic import 지원이 포함된다. 가능하면 target을 굳이 es2015로 낮추지 않는 게 안전하다.

예시:

// vite.config.ts
export default defineConfig({
  plugins: [react()],
  build: {
    target: "es2020", // 또는 "modules" / "esnext"
  },
});

 

해결 2) esbuild 버전 업그레이드

내 케이스에서는 target을 유지해야 하는 제약이 있어(레거시 정책 등) esbuild를 최신으로 올리는 방식으로 해결했다.

# npm
npm i -D esbuild@latest

# pnpm
pnpm add -D esbuild@latest

# yarn
yarn add -D esbuild@latest
 

Vite/번들러 체인에서 esbuild가 간접 의존성일 수도 있으니, 실제로 어떤 버전이 쓰이는지 확인하려면:

 
npm ls esbuild
# 또는
pnpm why esbuild

 

 

정리: “빌드 후 흰 화면”에서 가장 먼저 볼 체크리스트

  1. 콘솔 에러에 dynamic import, require, Failed to fetch dynamically imported module, chunk errorr 같은 문구가 있는지 확인
  2. React.lazy/라우트 코드스플리팅을 쓰고 있다면 build.target이 너무 낮게(es2015) 잡혀 있지 않은지 확인
  3. 가능하면 modules/es2020/esnext로 올리기
  4. 필요하면 esbuild 업그레이드 + 의존성 트리에서 실제 적용 버전 확인
  5. 그래도 재현되면 esbuild의 “dynamic import → require 변환”류 이슈를 의심