한 번에 다 불러오지 말고, 필요한 시점에 나눠서 불러와라
SPA(Single Page Application)는 한 번 로드되면 앱 전체를 빠르게 사용할 수 있다는 장점이 있습니다.
하지만 문제는 처음 로드되는 "한 번"이 너무 무겁다는 점이죠.
초기 번들이 너무 크면,
이때 필요한 전략이 바로 Code Splitting(코드 분할)입니다.
“앱 전체 코드를 한 덩어리로 불러오지 말고, 필요한 순간에 나눠서 불러온다.”
현대 프론트엔드는 하나의 번들 파일(bundle.js)에 다음이 모두 들어갑니다:
결과적으로 “첫 화면을 띄우기 위해 10만 줄의 코드”를 받게 됩니다.
| 구분 | 내용 |
|---|---|
| 초기 번들 크기 | 약 2MB |
| JS 파싱 시간 | 600~900ms |
| 첫 페인트 시점 (FCP) | 3.8초 |
| 사용자 체감 | “버벅인다”, “로딩이 너무 길다” |

하나의 번들로 묶인 구조 (Webpack 번들링 개념도)
.js, .css, .jpg, .png 등의 정적 자산(Static Assets) 이 생성됩니다.Code Splitting은 애플리케이션 코드를 여러 개의 작은 번들(Chunk)로 분리하여, 필요한 시점에만 네트워크로 로드하는 기법입니다.
즉, 앱을 기능 단위로 쪼개서 “사용자가 필요한 순간”에만 불러옵니다.
# 예시
main.js (기본 UI)
chart.js (통계 페이지용)
admin.js (관리자 페이지용)
이렇게 분할하면 초기 로딩 때 main.js만 다운로드하고, 관리자 페이지로 이동할 때 admin.js를 나중에 불러옵니다.

Code Splitting 구조도
bundle.js 안에 묶여 있어, 페이지를 처음 로드할 때 모든 스크립트가 한꺼번에 다운로드되었습니다.chunk.js) 로 나누어 사용자가 특정 페이지나 기능을 요청할 때만 해당 코드가 로드됩니다.Webpack, Vite, Rollup 같은 번들러는 모든 JS 파일의 의존 관계 그래프를 분석하여 하나의 파일로 묶습니다.
하지만 다음 조건을 만나면 자동으로 “청크(Chunk)”를 나눕니다.
1. 동적 import 사용 (import())
2. React.lazy() 등 lazy load 선언
3. Route-level Split (페이지 단위 분리)
// before
import Chart from './Chart';
// after
const Chart = React.lazy(() => import('./Chart'));
👉 import()가 등장하는 순간,
Webpack은 해당 모듈을 별도 청크(chart.chunk.js)로 분리합니다.
브라우저는 실제로 그 코드가 필요해질 때 네트워크 요청을 보냅니다.

Webpack 의존 그래프 구조도
bootstrap.main.ts) 은 애플리케이션의 Entry Point 로, Webpack이 번들링을 시작하는 진입점입니다.app.component.js) 에서 여러 모듈(external.lib.js, some.component.ts)로 의존 관계(Dependency) 가 확장됩니다..css, .sass, .dep.js)을 불러오며,페이지 전환 시 필요한 컴포넌트만 불러오는 방법입니다.
// routes/Dashboard.jsx
export default function Dashboard() {
return <h1>📊 Dashboard</h1>;
}
// App.jsx
import { Suspense, lazy } from 'react';
const Dashboard = lazy(() => import('./routes/Dashboard'));
const Settings = lazy(() => import('./routes/Settings'));
export default function App() {
return (
<Suspense fallback={<div>로딩 중...</div>}>
<Router>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Router>
</Suspense>
);
}
lazy()는 동적 import를 감싸는 React 도우미Suspense는 로딩 중 fallback UI를 보여주는 역할
Code Splitting 적용 전: 모든 스크립트가 한 번에 로드되는 모습
index.js, main.js, _app.js, webpack.js 등큰 라이브러리(예: Chart.js, Editor 등)는 초기 번들에서 제외하고 사용 시점에만 로드하도록 분할합니다.
const Chart = lazy(() => import('./components/HeavyChart'));
...
<Suspense fallback={<Spinner />}>
<Chart />
</Suspense>
👉 사용자가 “차트 영역”을 보기 전까지 관련 라이브러리(JS, CSS)는 네트워크 요청조차 발생하지 않습니다.
| 전략 | 설명 | 대표 예시 | 장점 | 주의점 |
|---|---|---|---|---|
| 라우트 기반(Route-level) | 페이지별 코드 분할 | /home, /settings | 간단, 가장 흔함 | 초기 라우트만 작음 |
| 영역 기반(View-level) | Fold 아래 섹션/모달 등 분할 | 탭, 드로어, 모달 | UX 매끄러움 | 관리 복잡 |
| 벤더 분리(Vendor Split) | 공통 라이브러리를 별도 청크로 | React, Lodash 등 | 캐시 재사용 ↑ | 초기 청크 관리 필요 |
| 조건 기반(Conditional) | 조건문 내 동적 import | 특정 기능 on/off | 메모리 효율 | 런타임 분기 주의 |
| 문제 | 설명 | 해결책 |
|---|---|---|
| ⚠️ 과도한 분할 | 청크가 너무 많으면 HTTP 요청 증가 | 청크 병합 정책 / HTTP2 병렬 요청 활용 |
| ⚠️ 초기 로딩 딜레이 | 필요한 코드가 늦게 로드되어 사용자 대기 | preload / prefetch로 사전 요청 |
| ⚠️ 중복 의존성 포함 | 동일 라이브러리가 여러 청크에 중복 포함 | 번들러 splitChunks.cacheGroups로 통합 |
| ⚠️ 캐싱 무효화 | 자주 변경되는 코드가 캐시를 깨뜨림 | 콘텐츠 해시 기반 파일명(.abc123.js) 사용 |
Code Splitting을 했더라도, 사용자가 자주 갈 페이지라면 미리 당겨받을 수도 있습니다.
<link rel="prefetch" href="/settings.chunk.js" />
<link rel="preload" href="/main.chunk.js" as="script" />
| 힌트 | 동작 시점 | 목적 |
|---|---|---|
| prefetch | 브라우저가 유휴 시간에 미리 요청 | “곧 쓸 리소스” 준비 |
| preload | 지금 바로 높은 우선순위로 요청 | “당장 필요한 리소스” 확보 |

Preload 적용 전후 렌더링 비교 (Financial Times 실험 예시)
| 도구 | 확인 포인트 |
|---|---|
| Chrome DevTools → Network | 청크 로드 시점 / 요청 순서 확인 |
| Lighthouse | “Reduce unused JavaScript” 개선 여부 |
| Coverage 탭 | 미사용 코드 비율 감소 확인 |
| Bundle Analyzer | 청크 간 중복 및 크기 분포 시각화 |

Code Splitting 후의 번들 크기 시각화 (Webpack Bundle Analyzer)
apexcharts, react-dom, react-slick 등)가, 오른쪽(src) 영역에는 애플리케이션 자체 코드(index.tsx, tailwind.css)가 포함되어 있습니다.apexcharts.common.js 등)을 분리하거나 Lazy Loading 대상으로 최적화할 수 있습니다.코드 분할은 단순히 파일을 나누는 기술이 아닙니다.
“사용자 경험을 기준으로 로딩 순서를 설계하는 전략”입니다.
초기 로딩이 가벼워질수록, 사용자는 앱이 즉시 반응한다고 느낍니다.
이것이 “빠름”의 심리적 체감 속도입니다.
필요한 시점에 필요한 코드만 로드하라.
번들러가 청크를 나누는 조건과 Webpack 의존 그래프 구조도를 보며 번들러가 code splitting을 어떻게 수행하는지 순차적으로 볼 수 있어 좋았습니다.
코드 스플리팅 외에도 preload/prefetch를 통해 렌더링 최적화 하는 부분에 대해서 새롭게 배워갑니다!