✂️ Code Splitting

Yujin Jung·2025년 11월 12일

한 번에 다 불러오지 말고, 필요한 시점에 나눠서 불러와라


✂️ 들어가며

SPA(Single Page Application)는 한 번 로드되면 앱 전체를 빠르게 사용할 수 있다는 장점이 있습니다.
하지만 문제는 처음 로드되는 "한 번"이 너무 무겁다는 점이죠.

초기 번들이 너무 크면,

  • 페이지 진입 속도는 느려지고,
  • 사용자는 첫 화면을 보기 전부터 지루해하며,
  • 브라우저는 파싱·실행하느라 메인 스레드를 점유합니다.

이때 필요한 전략이 바로 Code Splitting(코드 분할)입니다.

“앱 전체 코드를 한 덩어리로 불러오지 말고, 필요한 순간에 나눠서 불러온다.”


왜 코드 분할이 필요한가?

문제 상황: 번들 크기 폭발

현대 프론트엔드는 하나의 번들 파일(bundle.js)에 다음이 모두 들어갑니다:

  • React / Vue / Lodash 같은 공통 라이브러리
  • 모든 페이지/컴포넌트의 코드
  • 스타일, 폰트, 이미지 등 정적 리소스

결과적으로 “첫 화면을 띄우기 위해 10만 줄의 코드”를 받게 됩니다.

구분내용
초기 번들 크기약 2MB
JS 파싱 시간600~900ms
첫 페인트 시점 (FCP)3.8초
사용자 체감“버벅인다”, “로딩이 너무 길다”


하나의 번들로 묶인 구조 (Webpack 번들링 개념도)

  • 왼쪽: 여러 개의 모듈(자바스크립트, Sass, 이미지 등)이 서로 의존 관계를 맺고 있습니다.
  • 가운데: Webpack이 이 모든 파일을 하나의 번들(bundle) 로 묶어주는 과정입니다.
  • 오른쪽: 번들링 결과물로, .js, .css, .jpg, .png 등의 정적 자산(Static Assets) 이 생성됩니다.
    👉 이처럼 번들링은 프로젝트의 모든 자원을 하나로 압축하지만, 모든 코드가 한 번에 로드되어 초기 성능 저하를 초래할 수 있습니다. 따라서 이후 단계에서 Code Splitting(코드 분할) 을 통해 필요한 부분만 나누어 불러오는 최적화가 이루어집니다.

✂️ Code Splitting 개념

Code Splitting은 애플리케이션 코드를 여러 개의 작은 번들(Chunk)로 분리하여, 필요한 시점에만 네트워크로 로드하는 기법입니다.

즉, 앱을 기능 단위로 쪼개서 “사용자가 필요한 순간”에만 불러옵니다.

# 예시
main.js         (기본 UI)
chart.js        (통계 페이지용)
admin.js        (관리자 페이지용)

이렇게 분할하면 초기 로딩 때 main.js만 다운로드하고, 관리자 페이지로 이동할 때 admin.js를 나중에 불러옵니다.


Code Splitting 구조도

  • 상단: 기존에는 모든 코드가 하나의 bundle.js 안에 묶여 있어, 페이지를 처음 로드할 때 모든 스크립트가 한꺼번에 다운로드되었습니다.
  • 하단: Code Splitting을 적용하면, 코드를 여러 개의 chunk 파일(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)을 불러오며,
    Webpack은 이 관계들을 모두 연결해 의존 그래프를 형성합니다.
    👉 번들러는 이 그래프를 기반으로 코드 분할(Code Splitting) 이 필요한 지점을 자동으로 감지하고, 필요할 때만 해당 모듈을 로드하도록 청크를 생성합니다.

✂️ React에서 Code Splitting 적용하기

1️⃣ 라우트 기반 분할

페이지 전환 시 필요한 컴포넌트만 불러오는 방법입니다.

// 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 적용 전: 모든 스크립트가 한 번에 로드되는 모습

  • Chrome DevTools의 Network 탭에서 확인한 결과,페이지 진입 시 index.js, main.js, _app.js, webpack.js
    모든 스크립트 파일이 동시에 요청되고 있습니다.
  • 이런 구조에서는 사용자가 방문하지 않는 페이지의 코드까지 미리 로드되기 때문에 초기 로딩 시간이 길어지고, TTI(Time To Interactive) 도 지연됩니다.
    👉 Code Splitting을 적용하면, 이러한 스크립트 중 실제로 필요한 청크만 먼저 로드되어 초기 성능이 크게 개선됩니다.

2️⃣ 컴포넌트 단위 분할

큰 라이브러리(예: 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메모리 효율런타임 분기 주의

✂️ Code Splitting의 부작용과 주의점

문제설명해결책
⚠️ 과도한 분할청크가 너무 많으면 HTTP 요청 증가청크 병합 정책 / HTTP2 병렬 요청 활용
⚠️ 초기 로딩 딜레이필요한 코드가 늦게 로드되어 사용자 대기preload / prefetch로 사전 요청
⚠️ 중복 의존성 포함동일 라이브러리가 여러 청크에 중복 포함번들러 splitChunks.cacheGroups로 통합
⚠️ 캐싱 무효화자주 변경되는 코드가 캐시를 깨뜨림콘텐츠 해시 기반 파일명(.abc123.js) 사용

✂️ Code Splitting + Prefetch / Preload

Code Splitting을 했더라도, 사용자가 자주 갈 페이지라면 미리 당겨받을 수도 있습니다.

<link rel="prefetch" href="/settings.chunk.js" />
<link rel="preload" href="/main.chunk.js" as="script" />
힌트동작 시점목적
prefetch브라우저가 유휴 시간에 미리 요청“곧 쓸 리소스” 준비
preload지금 바로 높은 우선순위로 요청“당장 필요한 리소스” 확보


Preload 적용 전후 렌더링 비교 (Financial Times 실험 예시)

  • 상단(1: before preload): Preload를 적용하기 전, 초기 화면이 2.5초까지 거의 비어 있다가 3.0초 이후에야 주요 콘텐츠가 렌더링되기 시작합니다.
  • 하단(2: with preload): Preload를 적용한 뒤, 핵심 리소스가 사전에 요청되어 초기 렌더링 시점이 1초 이상 빨라졌습니다.
    👉 Preload는 “지금 필요한 리소스”를 미리 불러와 사용자에게 더 빠른 첫 화면(FCP, LCP)을 제공하는 효과를 냅니다.

✂️ 성능 검증 방법

도구확인 포인트
Chrome DevTools → Network청크 로드 시점 / 요청 순서 확인
Lighthouse“Reduce unused JavaScript” 개선 여부
Coverage 탭미사용 코드 비율 감소 확인
Bundle Analyzer청크 간 중복 및 크기 분포 시각화


Code Splitting 후의 번들 크기 시각화 (Webpack Bundle Analyzer)

  • 이 트리맵은 번들 파일(bundle.js) 내부의 구성 요소를 시각적으로 분석한 결과입니다.
  • 왼쪽(node_modules) 영역에는 외부 라이브러리(apexcharts, react-dom, react-slick 등)가, 오른쪽(src) 영역에는 애플리케이션 자체 코드(index.tsx, tailwind.css)가 포함되어 있습니다.
  • 각 블록의 크기는 해당 모듈이 번들 내에서 차지하는 파일 크기 비중을 의미합니다.
    👉 이처럼 Code Splitting을 적용하면, 필요한 라이브러리나 기능 단위로 번들을 나눠서 관리할 수 있고, 과도하게 큰 모듈(apexcharts.common.js 등)을 분리하거나 Lazy Loading 대상으로 최적화할 수 있습니다.

✂️ 마무리

코드 분할은 단순히 파일을 나누는 기술이 아닙니다.
“사용자 경험을 기준으로 로딩 순서를 설계하는 전략”입니다.

초기 로딩이 가벼워질수록, 사용자는 앱이 즉시 반응한다고 느낍니다.
이것이 “빠름”의 심리적 체감 속도입니다.

필요한 시점에 필요한 코드만 로드하라.

profile
매일매일 조금씩 성장하려 노력하는 프론트엔드 개발자입니다!

1개의 댓글

comment-user-thumbnail
2025년 11월 12일

번들러가 청크를 나누는 조건과 Webpack 의존 그래프 구조도를 보며 번들러가 code splitting을 어떻게 수행하는지 순차적으로 볼 수 있어 좋았습니다.

코드 스플리팅 외에도 preload/prefetch를 통해 렌더링 최적화 하는 부분에 대해서 새롭게 배워갑니다!

답글 달기