f8b21af4be
10_Wiki/Topics 대규모 정리: - 오류 캡처/미완성 stub 문서 227개 제거 - 교차폴더 중복 43클러스터 병합 (63파일 → redirect) - 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건 - 카테고리 MOC 6개 신규 생성 - Graph 섹션 미해결 related-keyword 링크 10,058건 제거 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
201 lines
5.2 KiB
Markdown
201 lines
5.2 KiB
Markdown
---
|
|
id: wiki-2026-0508-code-splitting
|
|
title: Code Splitting
|
|
category: 10_Wiki/Topics
|
|
status: verified
|
|
canonical_id: self
|
|
aliases: [Bundle Splitting, Dynamic Import]
|
|
duplicate_of: none
|
|
source_trust_level: A
|
|
confidence_score: 0.9
|
|
verification_status: applied
|
|
tags: [bundling, performance, webpack, vite, frontend]
|
|
raw_sources: []
|
|
last_reinforced: 2026-05-10
|
|
github_commit: pending
|
|
tech_stack:
|
|
language: TypeScript
|
|
framework: Vite / Webpack / Next.js
|
|
---
|
|
|
|
# Code Splitting
|
|
|
|
## 매 한 줄
|
|
> **"매 bundle 의 chunk 의 분리, on-demand load 의 first-paint 단축"**. Code Splitting 은 dynamic `import()` + bundler chunking 의 결합 의 매 monolithic JS bundle 의 분해. 2026 Vite/Rspack/Turbopack 시대 에 route-level + component-level + vendor split 의 매 표준.
|
|
|
|
## 매 핵심
|
|
|
|
### 매 splitting 의 axis
|
|
- **Route-based**: 각 page 의 separate chunk — 매 SPA / Next.js 의 default
|
|
- **Component-based**: heavy component 의 lazy (Modal, Chart, Editor)
|
|
- **Vendor**: `node_modules` 의 separate chunk — long-term cache
|
|
- **Dynamic feature**: locale, A/B variant 의 conditional load
|
|
|
|
### 매 mechanism
|
|
- **Dynamic `import()`**: ECMAScript spec — Promise 의 return
|
|
- **Bundler 의 split point detection**: `import()` 호출 의 chunk boundary
|
|
- **Manifest**: hashed filename 의 mapping
|
|
- **Preload / prefetch**: `<link rel="preload/prefetch">` hint
|
|
|
|
### 매 cost trade-off
|
|
- **Pro**: initial bundle 의 작아짐 → faster TTI
|
|
- **Con**: extra HTTP request, waterfall 위험 — preload 의 mitigate
|
|
|
|
### 매 응용
|
|
1. Route-level lazy (React Router, Next.js).
|
|
2. Modal / dialog (open 시 load).
|
|
3. Heavy editor (Monaco, CodeMirror).
|
|
4. Chart library (Recharts, ECharts).
|
|
5. i18n locale chunk.
|
|
|
|
## 💻 패턴
|
|
|
|
### React.lazy + Suspense
|
|
```tsx
|
|
import { lazy, Suspense } from 'react';
|
|
|
|
const Settings = lazy(() => import('./Settings'));
|
|
|
|
export default function App() {
|
|
return (
|
|
<Suspense fallback={<Spinner />}>
|
|
<Settings />
|
|
</Suspense>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Next.js dynamic
|
|
```tsx
|
|
import dynamic from 'next/dynamic';
|
|
|
|
const Chart = dynamic(() => import('@/components/Chart'), {
|
|
loading: () => <p>Loading chart...</p>,
|
|
ssr: false, // browser-only
|
|
});
|
|
|
|
export default function Dashboard() {
|
|
return <Chart data={data} />;
|
|
}
|
|
```
|
|
|
|
### Route-level (React Router v6)
|
|
```tsx
|
|
import { lazy } from 'react';
|
|
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
|
|
|
const router = createBrowserRouter([
|
|
{ path: '/', lazy: () => import('./routes/Home') },
|
|
{ path: '/about', lazy: () => import('./routes/About') },
|
|
{ path: '/admin/*', lazy: () => import('./routes/Admin') },
|
|
]);
|
|
|
|
export default function App() {
|
|
return <RouterProvider router={router} />;
|
|
}
|
|
```
|
|
|
|
### Vite의 manualChunks
|
|
```ts
|
|
// vite.config.ts
|
|
import { defineConfig } from 'vite';
|
|
|
|
export default defineConfig({
|
|
build: {
|
|
rollupOptions: {
|
|
output: {
|
|
manualChunks: {
|
|
react: ['react', 'react-dom'],
|
|
ui: ['@radix-ui/react-dialog', '@radix-ui/react-tabs'],
|
|
charts: ['recharts', 'd3'],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
```
|
|
|
|
### Webpack magic comment
|
|
```ts
|
|
const Editor = () => import(
|
|
/* webpackChunkName: "editor" */
|
|
/* webpackPrefetch: true */
|
|
'./Editor'
|
|
);
|
|
```
|
|
|
|
### Conditional import (locale)
|
|
```ts
|
|
async function loadLocale(lang: string) {
|
|
const messages = await import(`./locales/${lang}.json`);
|
|
return messages.default;
|
|
}
|
|
```
|
|
|
|
### Preload critical chunk
|
|
```html
|
|
<link rel="modulepreload" href="/assets/Settings-abc123.js">
|
|
```
|
|
|
|
```tsx
|
|
// React: preload on hover
|
|
<Link
|
|
to="/settings"
|
|
onMouseEnter={() => import('./Settings')}
|
|
>
|
|
Settings
|
|
</Link>
|
|
```
|
|
|
|
### Module Federation (micro-frontend)
|
|
```ts
|
|
// host webpack.config.js
|
|
new ModuleFederationPlugin({
|
|
remotes: {
|
|
checkout: 'checkout@https://cdn/checkout/remoteEntry.js',
|
|
},
|
|
});
|
|
|
|
// usage
|
|
const Checkout = lazy(() => import('checkout/Cart'));
|
|
```
|
|
|
|
## 매 결정 기준
|
|
| 상황 | Approach |
|
|
|---|---|
|
|
| SPA, multi-page | Route-based split |
|
|
| Heavy modal / editor | `lazy()` + Suspense |
|
|
| Vendor lib stable | manualChunks vendor split |
|
|
| SSR + browser-only | `dynamic({ ssr: false })` |
|
|
| Independent deploy | Module Federation |
|
|
| Hover-triggered | preload on intent |
|
|
|
|
**기본값**: route-based + heavy-component lazy + vendor chunk.
|
|
|
|
## 🔗 Graph
|
|
- 부모: [[Web Performance]]
|
|
- 변형: [[Module Federation]]
|
|
- 응용: [[Lazy-Loading-Strategies|Lazy Loading]]
|
|
- Adjacent: [[Vite]] · [[Next.js]] · [[Suspense]]
|
|
|
|
## 🤖 LLM 활용
|
|
**언제**: route lazy, heavy component 의 split, vendor chunking 코드 generation.
|
|
**언제 X**: 매 small app (10kb 의 split 의 overhead 가 더 큼).
|
|
|
|
## ❌ 안티패턴
|
|
- **너무 fine-grained split**: 100개 chunk → request flood — 매 ~10-30 chunk 의 sweet spot.
|
|
- **Waterfall**: A → B → C 의 sequential — 매 parallel preload.
|
|
- **Vendor chunk 의 무한 growth**: dependency 추가 시 cache invalidation — 매 stable lib 만 vendor.
|
|
- **Suspense 의 boundary 누락**: error / blank screen.
|
|
- **`ssr: false` 의 abuse**: SEO 손실.
|
|
|
|
## 🧪 검증 / 중복
|
|
- Verified (Vite docs, Webpack docs, Next.js docs, React 19).
|
|
- 신뢰도 A.
|
|
|
|
## 🕓 Changelog
|
|
| 날짜 | 변경 |
|
|
|---|---|
|
|
| 2026-05-08 | Phase 1 |
|
|
| 2026-05-10 | Manual cleanup — splitting strategies + bundler config + MF |
|