좋아. 이제 우리가 다루는 건 React 앱의 구조적 성능 최적화,
즉 "코드를 잘게 쪼개서 필요한 시점에만 로딩되게 하는 전략"이야.
이번 주제는 Code Splitting, Dynamic Import, Bundle 분석 및 최적화에 초점을 맞춘다.
이는 사용자가 처음 접속할 때 로딩 속도를 획기적으로 줄이는 핵심 기법이다.
1. 왜 번들이 문제인가?
React 앱이 느린 이유는 종종 네트워크나 서버가 아니라,
**“한꺼번에 너무 많은 자바스크립트 코드가 로드되기 때문”**이다.
현대 웹 앱은 수십 개의 페이지, 수백 개의 컴포넌트로 구성되는데,
빌드 시 모두 하나의 거대한 번들로 묶이면 이런 문제가 생긴다.
“사용자가 홈 화면만 열었는데, 관리자 페이지 코드까지 다 받는다.”
이런 불필요한 코드 전달이 Initial Load Time(초기 로딩 시간) 을 늦춘다.
2. Code Splitting이란?
Code Splitting은
“필요한 코드만, 필요한 순간에 로드하는 것” 을 말한다.
React에서는 Dynamic Import 를 이용해 손쉽게 구현할 수 있다.
예시
import React, { lazy, Suspense } from "react";
const Dashboard = lazy(() => import("./Dashboard"));
function App() {
return (
<Suspense fallback={<div>로딩 중...</div>}>
<Dashboard />
</Suspense>
);
}
✅ lazy()
→ 번들을 자동으로 분리하여 Dashboard.js 를 별도 청크로 생성
✅ Suspense
→ 해당 컴포넌트가 로딩될 때 표시할 임시 UI 제공
➡ 페이지별 코드 로드가 분리되어 초기 번들 크기 대폭 감소.
3. 라우팅 단위 Code Splitting
가장 효과적인 방법은 페이지 라우트별로 코드 스플리팅 하는 것이다.
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { lazy, Suspense } from "react";
const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));
const Admin = lazy(() => import("./pages/Admin"));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>페이지 로딩 중...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/admin" element={<Admin />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
💡 결과:
- / 방문 시 Home만 로드
- /admin 방문 전까지 Admin.js는 네트워크 요청조차 없음
즉, “한 번에 다 불러오는 SPA 구조” 대신
“필요할 때 불러오는 MPA-like 구조” 로 바뀐다.
4. Dynamic Import — 조건부 로딩
어떤 UI나 기능은 “항상 필요하지 않다.”
예를 들어 차트, 지도, 대시보드 위젯 등은 조건부 로드로 처리 가능하다.
import React, { useState, Suspense, lazy } from "react";
const Chart = lazy(() => import("./Chart"));
export default function Dashboard() {
const [showChart, setShowChart] = useState(false);
return (
<div>
<button onClick={() => setShowChart(true)}>차트 보기</button>
{showChart && (
<Suspense fallback={<p>차트 불러오는 중...</p>}>
<Chart />
</Suspense>
)}
</div>
);
}
✅ 차트는 버튼 클릭 전까지 로드되지 않음
✅ 메인 번들에서 차트 관련 코드 제거
✅ 초기 JS 크기 30~40% 감소 가능
5. Bundle 분석 — 어디서 무거워지는가?
5-1. Webpack Bundle Analyzer
npm install --save-dev webpack-bundle-analyzer
package.json에 추가 👇
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'"
}
혹은 Next.js의 내장 분석기 사용:
npm install @next/bundle-analyzer
next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({});
✅ 시각적으로 어떤 라이브러리가 가장 무거운지 확인 가능
✅ 예: moment.js, lodash, chart.js, mapbox 등
6. Bundle 크기를 줄이는 실전 전략
항목 설명 예시
| Tree Shaking | 사용하지 않는 코드 제거 | import { debounce } from 'lodash-es' |
| Dynamic Import | 조건부 로드 | import('./Chart') |
| 코드 중복 제거 | 공통 컴포넌트 따로 분리 | /shared 디렉토리 관리 |
| 라이브러리 교체 | 경량 대체 라이브러리 | moment → dayjs, lodash → lodash-es |
| 이미지/폰트 최적화 | WebP, AVIF 포맷 | next/image 활용 |
| 환경별 번들 분리 | Dev/Prod 빌드 분리 | minify, gzip, brotli |
7. React.lazy + Suspense 최적 패턴
Suspense는 단순 fallback UI 외에도,
UX적으로 매끄러운 로딩 체감을 설계할 수 있다.
예를 들어,
<Suspense fallback={<LoadingOverlay />}>
<LazyHeavyChart />
</Suspense>
LoadingOverlay를 단순한 텍스트 대신
Skeleton UI나 Progress Bar로 구성하면
사용자는 “로딩 중”이라기보다 “진행 중”으로 인식하게 된다.
8. Next.js에서의 코드 분할
Next.js는 이미 내부적으로 라우트 기반 코드 스플리팅을 수행한다.
그러나 동적 import를 활용하면 더 세밀하게 제어 가능하다.
import dynamic from 'next/dynamic';
const Editor = dynamic(() => import('../components/Editor'), {
ssr: false,
loading: () => <p>에디터 불러오는 중...</p>,
});
✅ 클라이언트 전용 컴포넌트 (SSR 비활성화)
✅ JS 파일이 별도의 청크로 분리되어 로딩
9. 캐싱 및 번들 압축
Gzip → Brotli
Brotli는 Gzip보다 약 20~25% 더 높은 압축률을 제공한다.
Vercel, Cloudflare, Nginx 등 대부분의 배포 환경은 자동 Brotli 지원.
gzip off;
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/javascript;
Cache-Control 헤더
Cache-Control: public, max-age=31536000, immutable
→ 빌드된 JS/CSS 파일에 캐시를 강하게 설정 (파일명에 해시 포함 필수)
10. 실무에서 자주 쓰는 최적화 조합
목적 방법
| 초기 로딩 시간 단축 | 페이지별 코드 스플리팅 + Dynamic Import |
| JS 번들 크기 감소 | Tree Shaking + 라이브러리 교체 |
| UX 자연스러움 | Suspense + Skeleton UI |
| 네트워크 최적화 | Brotli 압축 + Cache-Control 헤더 |
| 느린 구간 파악 | Bundle Analyzer 시각화 |
11. 성능 개선 전후 비교
항목 개선 전 개선 후
| 초기 JS 번들 크기 | 1.6MB | 590KB |
| LCP | 3.8초 | 1.9초 |
| TTI (Time to Interactive) | 4.2초 | 2.3초 |
| Lighthouse Performance | 61점 | 94점 |
12. 마무리
Code Splitting과 Dynamic Import는
“한 번에 다 불러오는 SPA 구조”를
“필요할 때 로딩하는 유연한 웹앱”으로 바꾸는 가장 강력한 기술이다.
코드 크기를 줄이는 건 단순한 성능이 아니라,
UX와 비용 모두를 최적화하는 일이다.
📌 키워드:
Code Splitting, Dynamic Import, Bundle Analyzer, React.lazy, Suspense, 번들 최적화, Lazy Load
📌 태그:
#React성능 #CodeSplitting #DynamicImport #Suspense #Bundle최적화
'frontend > javascript' 카테고리의 다른 글
| 🟨 2-39. 실전 성능 종합 튜닝 — React + Next.js 프로젝트 최적화 로드맵 (0) | 2025.11.07 |
|---|---|
| 🟨 2-38. 이미지·폰트·애니메이션 최적화 — 시각적 성능 튜닝의 모든 것 (0) | 2025.11.07 |
| 🟨 2-36. React 성능 병목 진단법 — Re-render, Virtual DOM, Memoization 완전 정리 (0) | 2025.11.07 |
| 🟨 2-35. React 기반 UX 성능 튜닝 실전 — Suspense, Streaming, Transition 완전 이해 (0) | 2025.11.07 |
| 🟨 2-34. 사용자가 체감하는 “빠른 웹” 만들기 — UX 성능 최적화와 인지 부하 설계 (0) | 2025.11.07 |