--- id: react-code-splitting title: React 코드 분할 — Lazy / Suspense / Route 단위 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [react, code-splitting, performance, lazy, vibe-coding] tech_stack: { language: "TypeScript / Vite / Webpack / Next", applicable_to: ["Web"] } applied_in: [] aliases: [React.lazy, dynamic import, chunk, bundle] --- # React 코드 분할 > 초기 번들이 크면 first paint 늦어짐. **route 단위 + 큰 라이브러리 단위로 lazy load**. 작은 컴포넌트 단위 분할은 오히려 waterfall. ## 📖 핵심 개념 - `React.lazy(() => import('./Module'))` + `` 페어. - Vite/Webpack 이 `import()` 만나면 자동 chunk 분리. - Next/RR 는 route 단위 자동 분할. - Prefetch: 사용자가 진짜 갈 가능성 높은 chunk 미리. ## 💻 코드 패턴 ### Route 단위 ```tsx import { lazy, Suspense } from 'react'; import { Routes, Route } from 'react-router-dom'; const Home = lazy(() => import('./pages/Home')); const Dashboard = lazy(() => import('./pages/Dashboard')); const Admin = lazy(() => import('./pages/Admin')); }> } /> } /> } /> ``` ### 무거운 라이브러리 lazy ```tsx // 사용자가 PDF 보고서 누를 때만 로드 const PDFViewer = lazy(() => import('./PDFViewer')); {showPDF && ( }> )} ``` ### Prefetch on hover ```tsx function NavLink({ to, importFn, children }) { return ( importFn()} // chunk 미리 > {children} ); } import('./pages/Dashboard')}>대시보드 ``` ### Webpack/Vite hint ```tsx // 항상 같이 쓸 chunk const Dashboard = lazy(() => import(/* webpackPrefetch: true */ './pages/Dashboard')); // 자주 쓸 chunk const Home = lazy(() => import(/* webpackPreload: true */ './pages/Home')); ``` ### 동적 polyfill ```tsx async function ensureIntl() { if (typeof Intl.RelativeTimeFormat === 'undefined') { await import('@formatjs/intl-relativetimeformat/polyfill'); } } ``` ## 🤔 의사결정 기준 | 분할 단위 | 권장 | |---|---| | Route | ✅ — 항상 | | 모달 / 큰 widget (chart, editor) | ✅ — 사용자가 트리거할 때만 로드 | | 작은 자주 쓰는 컴포넌트 | ❌ — overhead 더 큼 | | 의존성 무거운 라이브러리 (chart.js, three.js, mapbox) | ✅ | | 일부 사용자만 쓰는 기능 (admin, beta) | ✅ | | 폴리필 | 조건부 dynamic import | ## ❌ 안티패턴 - **모든 컴포넌트 lazy**: chunk 폭증, waterfall. 큰 단위로. - **lazy 안에 named export 직접**: `lazy(() => import('./X')).default` — default export 필요. named 면 `.then(m => ({ default: m.X }))`. - **Suspense 경계 없음**: lazy 컴포넌트 사용 시 throw — 화면 깨짐. - **prefetch 모든 link**: 의도와 다른 chunk 많이 다운로드. hover 또는 viewport 기반. - **chunk 이름 무관심**: webpack 이 number 만 — 디버깅 어려움. magic comment `webpackChunkName`. - **dev 모드에서만 빠르고 prod 첫 방문 느림**: prod 번들 크기 측정 (rollup-plugin-visualizer / source-map-explorer). - **CSS 도 분할 안 함**: 큰 CSS는 별도 chunk. ## 🤖 LLM 활용 힌트 - "Route 단위 + 큰 라이브러리 단위로만 lazy. 작은 단위 X" 강조. - prefetch on hover 패턴. ## 🔗 관련 문서 - [[React_Suspense_for_Data]] - [[React_Router_Patterns]]