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.의도치 않은 번들 합침에 대비

  1. 레이아웃, 페이지 파일은 use client 금지
  2. 꼭 필요한 부분만 분리할 것 (너무 많이 분리하면 오히려 성능 하락)
  3. 무거운 라이브러리는 클라이언트 파일에서 import ( chart, editor, map, excel, pdf 렌더러)
  4. 상호작용 없는 UI는 서버 컴포넌트
  5. 데이터만 표시하는 목록/카드는 서버 컴포넌트 → 클라 번들 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 탭에서 첫 페인트 전 네트워크/스크립트 병목 확인