Code Splitting
1.Next.js에서 코드 스플리팅의 기본 개념
- 초기 번들 크기를 줄여 **첫 로딩/하이드레이션 시간(LCP, TTI)**을 단축
- "지금 필요한 코드만” 내려 받아 체감 성능을 개선
- 라우트/컴포넌트 단위로 독립적인 청크(chunks) 생성 → 캐시 효율↑, 오류 격리↑
2.라우트(페이지) 단위 코드 스플리팅
1) 자동 분할 (App Router)
app/의 각 라우트(segment)는 자동으로 별도 청크가 됩니다.
- 즉,
/dashboard, /settings는 기본적으로 서로 다른 번들로 나뉩니다.
- 팁: 루트에
use client를 두지 마세요. 상위에 한 번만 두면 아래 트리 전체가 클라이언트 번들로 합쳐져 청크가 커집니다.
// app/(marketing)/page.tsx → 별도 청크
export default function Marketing() { /* ... */ }
// app/(app)/dashboard/page.tsx → 또 다른 청크
export default function Dashboard() { /* ... */ }
3.컴포넌트 단위 코드 스플리팅: next/dynamic
- 스크롤 후/상호작용 후 등장하는 **무거운 위젯(차트/에디터/맵)**을 지연 로딩
// app/dashboard/_components/HeavyChart.tsx (일반 컴포넌트)
"use client";
export default function HeavyChart() { /* chart lib 사용 */ }
// app/dashboard/page.tsx
import dynamic from "next/dynamic";
const HeavyChart = dynamic(() => import("./_components/HeavyChart"), {
loading: () => <p>차트를 불러오는 중…</p>, // 로딩 플레이스홀더
});
export default function Page() {
return (
<section>
<h1>대시보드</h1>
<HeavyChart /> {/* 여기가 실제 접근될 때 청크 로드 */}
</section>
);
}
- 브라우저 API를 쓰는 라이브러리, DOM 의존 위젯은 SSR을 끄면 안전&가벼움
const Map = dynamic(() => import("./Map"), { ssr: false });
// → 서버 렌더 제외. 클라이언트에서만 import 하므로 서버 번들에 섞이지 않음.
- 이름 있는 내보내기(named export) 로드
const Editor = dynamic(() => import("./Editor").then(m => m.Editor));
- 이벤트 기반 지연 로드(진짜로 “필요할 때만”)
import dynamic from "next/dynamic";
import { useState } from "react";
export default function Page() {
const [open, setOpen] = useState(false);
const LazyModal = open
? dynamic(() => import("./Modal")) // 열릴 때만 import
: null;
return (
<>
<button onClick={() => setOpen(true)}>모달 열기</button>
{open && LazyModal && <LazyModal />}
</>
);
}
4.Suspense와 스트리밍을 이용한 분할 표시
- App Router는 React Suspense + 스트리밍을 지원 → 상단 콘텐츠 먼저, 무거운 컴포넌트는 나중에
// app/page.tsx (Server Component)
import { Suspense } from "react";
import AboveTheFold from "./AboveTheFold";
import BelowHeavy from "./BelowHeavy";
export default function Page() {
return (
<>
<AboveTheFold /> {/* 바로 스트리밍 */}
<Suspense fallback={<p>아래 콘텐츠 로딩…</p>}>
{/* 내부에 dynamic 컴포넌트가 있어도 OK */}
<BelowHeavy />
</Suspense>
</>
);
}
5.의도치 않은 번들 합침에 대비
- 레이아웃, 페이지 파일은 use client 금지
- 꼭 필요한 부분만 분리할 것 (너무 많이 분리하면 오히려 성능 하락)
- 무거운 라이브러리는 클라이언트 파일에서 import ( chart, editor, map, excel, pdf 렌더러)
- 상호작용 없는 UI는 서버 컴포넌트
- 데이터만 표시하는 목록/카드는 서버 컴포넌트 → 클라 번들 0B
6.Webpack 청크 최적화와 패키지 트리쉐이킹
- date-fns, lodash-es, lucide-react 등 함수/아이콘 단위로만 번들에 포함
// next.config.js
module.exports = {
experimental: {
optimizePackageImports: ["date-fns", "lodash-es", "lucide-react"],
},
};
2) ESM 지원 패키지 사용
가) lodash 대신 lodash-es
나) 아이콘은 패키지 전체가 아닌 개별 아이콘 경로 import
// 좋음
import { addDays } from "date-fns";
import { Calendar } from "lucide-react";
// 나쁨(패키지 전체 로드 위험)
import * as dateFns from "date-fns";
3) 사이드이펙트 방지
가) 유틸 모듈에 전역 부작용(side effects) 코드를 두지 말 것 → 트리쉐이킹 저해
7.링크 관련은 Prefetch 되는 것 확인
- nextjs/link 사용시 해당 링크 내용 prefetch 되는 것을 이용.(기본 default값 true임)
import Link from "next/link";
<Link href="/dashboard" prefetch>
대시보드로
</Link>
8.측정과 검증(꼭 하세요)
1) 번들 분석기
가) @next/bundle-analyzer로 페이지별 청크 크기 확인
// next.config.js
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
module.exports = withBundleAnalyzer({
// your config
});
나) 실행
ANALYZE=true next build
# .next/analyze 에서 청크 그래프 확인
2) 크롬 성능 지표
- Lighthouse로 LCP/TTI/CLS, Coverage 탭으로 미사용 코드 확인
- Performance 탭에서 첫 페인트 전 네트워크/스크립트 병목 확인