초기 진입 JS 번들 100KB 이하 유지를 목표로 한다.
| 항목 | 목표 |
|---|---|
| 초기 JS (First Load JS) | 100KB 이하 |
| 페이지별 청크 | 50KB 이하 |
| 이미지 (LCP 대상) | WebP/AVIF, 200KB 이하 |
next build 출력에서 First Load JS가 100KB를 초과하면 즉시 최적화한다.
무거운 라이브러리, 차트, 에디터, 모달은 첫 번들에 포함하지 않는다.
// ✅ 차트 — 뷰포트 진입 또는 탭 전환 시 로드
import dynamic from "next/dynamic";
const Chart = dynamic(() => import("@/src/widgets/chart/ui/chart"), {
ssr: false, // 브라우저 전용 라이브러리
loading: () => <Skeleton />,
});
// ✅ 모달 — 버튼 클릭 시 로드
const Modal = dynamic(() => import("@/src/shared/ui/modal"));
ssr: false는 window, document 등 브라우저 API를 직접 쓸 때만서버 컴포넌트는 클라이언트 번들에 포함되지 않는다. 인터랙션 없는 UI는 서버 컴포넌트로 유지한다.
❌ 조회 전용 페이지에 'use client' → 전체가 번들에 포함
✅ 서버 컴포넌트 기본 → 인터랙션 부분만 'use client' 분리
// ✅ next/image — WebP 자동 변환, lazy load, CLS 방지
import Image from "next/image";
<Image
src="/hero.png"
alt="hero"
width={1200}
height={630}
priority // LCP 대상 이미지만
/>
priority는 LCP(Largest Contentful Paint) 대상 1개에만 사용next.config.ts의 remotePatterns에 도메인 등록// ✅ next/font — 폰트 다운로드 대기 없이 레이아웃 시프트 방지
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"], display: "swap" });
@import url(...) 직접 사용 금지 — 렌더 블로킹 발생// ✅ next/script — 메인 번들과 분리 로드
import Script from "next/script";
<Script src="https://analytics.example.com/script.js" strategy="afterInteractive" />
| strategy | 설명 | 용도 |
|---|---|---|
beforeInteractive | HTML 파싱 전 | 필수 폴리필만 |
afterInteractive | hydration 후 | GA, GTM |
lazyOnload | idle 시 | 채팅 위젯, 비필수 |
ANALYZE=true next build
@next/bundle-analyzer 설치 후 사용. 큰 모듈 파악 → dynamic import 또는 경량 대체재 탐색.
next build 후 First Load JS 100KB 이하 확인priority 속성next/font로 로드next/script로 분리<img> 태그 직접 사용 (next/image 사용)<link rel="stylesheet"> 또는 @import로 폰트 로드strategy="beforeInteractive"를 GA/GTM에 사용아직 피드백이 없어요. 첫 번째로 의견을 남겨보세요!