좋아, 이제까지 흩어져 있던 성능 최적화 내용을
“실제 Next.js + React 프로젝트 하나를 처음부터 끝까지 튜닝한다” 는 관점에서 한 번에 정리해보자.
지금 보는 걸 그대로 “체크리스트”로 쓰면 된다.
신규 프로젝트든, 이미 운영 중인 서비스든 이 로드맵 그대로 점검하면 꽤 단단한 성능을 만들 수 있어.
1. 설계 단계(코드 쓰기 전)에서 할 일
성능은 코드를 쓰기 전에 이미 절반 정도 결정된다.
1) 렌더링 전략부터 고르기 (SSR / SSG / ISR / CSR)
Next.js 기준으로, 각 페이지마다 “어떻게 렌더링할지”를 먼저 설계하자.
- SSG (정적 생성)
- 자주 안 바뀌는 페이지: 블로그 글, 상품 상세, 설명 페이지
- generateStaticParams, fetch (캐시 사용) 등
- ISR (Incremental Static Regeneration)
- 데이터는 바뀌지만 초 단위로 꼭 최신일 필요는 없을 때
- 예: 상품 리스트, 인기글, 랭킹
- revalidate 옵션으로 주기적 재생성
- SSR
- 사용자의 상태(로그인, 권한)에 따라 매번 다를 때
- 예: 마이페이지, 관리자 대시보드 등
- CSR
- 클라이언트에서만 의미가 있는 페이지 (에디터, 대시보드 위젯 등)
처음부터 “모든 걸 SSR로 박는” 순간,
오리진 서버는 트래픽 올라갈 때마다 고통받게 된다.
2) 라우트/기능별 코드 분할 계획
- “페이지 단위로 코드 스플리팅”을 기본 전략으로 깔고
- 그 위에 “특히 무거운 컴포넌트”는 동적 import로 한 번 더 쪼갠다.
예:
- /admin, /chart, /map 같은 페이지는 보통 무겁다 → 나중에 로드되게
- WYSIWYG 에디터, 차트, 지도, 파일 업로더 같은 건 버튼 눌렀을 때만 로드
3) 상태 관리 구조 설계
전역 상태는 정말 필요한 최소한에만 쓰고,
나머지는 가능하면 페이지/컴포넌트 단위 로컬 상태로 둔다.
- 전역 상태 라이브러리(예: Redux, Zustand)는
- “여러 페이지에서 동시에 필요”한 데이터 위주로
- React Query / SWR은
- 서버 상태(백엔드 데이터) 캐싱 전담
이렇게 역할을 나누면 쓸데없는 리렌더링과 오버페칭을 크게 줄일 수 있다.
4) 이미지 · 폰트 · 아이콘 전략 미리 정하기
- 이미지:
- Next.js next/image 사용
- WebP/AVIF 사용, Lazy Load 기본
- 폰트:
- next/font 또는 @font-face + font-display: swap
- 필요한 폰트만, 굵기도 최소한으로
- 아이콘:
- 가능한 SVG 기반 (아이콘 폰트 X)
- 자주 쓰는 아이콘 세트는 컴포넌트화
설계 단계에서 이 방향만 박아두면
나중에 “이미지, 폰트 때문에 LCP 터지는” 상황을 훨씬 덜 본다.
2. 개발 단계 — 컴포넌트/로직 성능 튜닝
1) 렌더링 비용 줄이기
- React.memo
- 부모 state 변경과 상관없는 컴포넌트에 적용
- useCallback / useMemo
- 정말 자주 렌더되는 컴포넌트 + 연산 무거운 부분에만 적용
- 리스트 최적화
- 긴 리스트 → react-window / react-virtualized
- Context 남발 금지
- 전역 Context 하나에 다 때려넣지 말고, 도메인별로 분리하거나 Zustand/Redux selector 사용
패턴:
“자주 바뀌는 것”과 “잘 안 바뀌는 것”을 최대한 분리한다.
2) 비동기 + UX: Suspense, Transition
- 데이터 로딩 부분은 Suspense + Skeleton UI
- 무거운 필터/검색/정렬은 useTransition 으로 감싸기
예:
const [isPending, startTransition] = useTransition();
const handleFilter = (value: string) => {
startTransition(() => {
setFilter(value);
});
};
사용자는 “입력이 끊기지 않는다”는 느낌을 받게 된다.
3) API 통신 & 캐싱
- React Query or SWR로
- 동일한 API 호출은 한 번만 서버로 가게 하고
- 캐시된 데이터는 즉시 사용 + 백그라운드에서 최신화(SWR 패턴)
const { data } = useQuery({
queryKey: ["products", category],
queryFn: fetchProducts,
staleTime: 60 * 1000,
});
- 자주 보는 데이터 → staleTime 길게
- 민감한 데이터 → cacheTime/staleTime 짧게
이렇게 하면 트래픽이 몰려도 서버 부하가 덜하다.
3. 빌드/번들 단계 — Bundle 크기 줄이기
1) Bundle Analyzer로 구성 파악
Next.js라면:
npm install @next/bundle-analyzer
next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({});
- ANALYZE=true npm run build
- 어느 페이지에서 어떤 라이브러리가 번들을 잡아먹는지 시각적으로 확인
보통 범인:
- chart 라이브러리
- date 라이브러리(moment)
- 전체 lodash
- 아이콘 패키지 전부 import
2) 가벼운 대체재로 교체
- moment → dayjs, date-fns
- lodash → lodash-es + 필요한 함수만 import
- chart.js → 필요하면 dynamic import + SSR off
import debounce from "lodash-es/debounce";
3) Dynamic Import 적극 활용
Next.js 예:
const Editor = dynamic(() => import('@/components/Editor'), {
ssr: false,
loading: () => <p>에디터 로딩 중...</p>,
});
- 관리자, 대시보드, 에디터, 차트처럼 “초기에 안 필요한 것들”은 전부 나중에 로드
- 초기 진입 번들 사이즈를 1/2~1/3까지 줄일 수 있다.
4. 인프라/배포 단계 — CDN · 캐시 · 압축
1) CDN 사용은 사실상 필수
- Next.js + Vercel, Cloudflare Pages, Netlify
- 기본적으로 전 세계 엣지 서버에서 정적 리소스 제공
- 정적 파일(JS, CSS, 이미지, 폰트)은 최대한 오리진에서 떼어낸다.
2) 압축
- Brotli 활성화 (Vercel, Cloudflare는 기본 지원)
- Nginx라면:
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/javascript;
3) 캐시 헤더
- 해시가 붙은 번들 파일:
- Cache-Control: public, max-age=31536000, immutable
- HTML/JSON/APIs:
- 데이터 성격에 맞게 max-age, s-maxage, stale-while-revalidate 조합
5. 런타임 모니터링 — 배포 후 계속 지켜보기
1) Lighthouse CI
- GitHub Actions / GitLab CI에 붙여서
- main 브랜치에 푸시될 때마다 성능 점수 체크
- 기준 점수 미만이면 빌드 실패 처리 가능
2) Web Vitals 수집
- LCP, FID, CLS 값을 실제 사용자 기준으로 수집
- Google Analytics, Sentry, 자체 API 등으로 전송
import { getLCP, getCLS, getFID } from "web-vitals";
getLCP(report);
getFID(report);
getCLS(report);
function report(metric) {
navigator.sendBeacon("/api/vitals", JSON.stringify(metric));
}
3) Sentry / LogRocket / Datadog 등
- JS 에러, 느린 페이지, 느린 API, rage click 같은 UX 문제까지 추적
이렇게 해 두면,
“개발할 땐 빨랐는데 어느 순간 느려짐” 같은 현상을 바로 잡을 수 있다.
6. 실제 프로젝트용 종합 체크리스트
아래는 Next.js + React 서비스 하나 기준의 성능 체크 가이드다.
티스토리 글 끝에 붙여도 딱 좋을 정도의 느낌으로 정리해보면 👇
🔹 설계 단계
- 페이지별 SSR/SSG/ISR/CSR 전략 결정
- 공통 레이아웃, 헤더/푸터 구조 설계
- 상태 관리(전역 vs 로컬 vs 서버 상태) 구분
- 이미지/폰트/CDN 전략 미리 정함
🔹 개발 단계
- React.memo / useCallback / useMemo 필요한 곳에만 적용
- 긴 리스트는 Virtualized List 사용
- Suspense + Skeleton으로 로딩 UX 구성
- React Query/SWR로 서버 상태 캐싱
- Context는 도메인별로 쪼개고 구독 최소화
🔹 빌드 단계
- Bundle Analyzer로 무거운 모듈 확인
- dynamic import로 무거운 컴포넌트/페이지 분리
- moment, lodash 등은 경량 대체재 사용
- 불필요한 polyfill, legacy 코드 제거
🔹 배포/인프라 단계
- CDN 활성화 (정적 파일 엣지에서 제공)
- Brotli 압축 또는 Gzip 설정 확인
- Cache-Control 헤더 적절히 설정
- 이미지/폰트 Preload, Lazy Load 적용
🔹 운영/모니터링 단계
- Lighthouse CI로 성능 점수 자동 점검
- Web Vitals를 실제 사용자 기준으로 수집
- Sentry/에러 추적 도구로 JS 에러, 느린 구간 모니터링
- 정기적으로 번들/성능 리포트 확인
7. 마무리
이제까지 나눠서 이야기했던 모든 성능 최적화 주제를
“Next.js + React 서비스 하나” 기준으로 한 번에 꿰어본 거라 보면 된다.
정리하자면,
- 렌더링 전략을 먼저 고르고
- 컴포넌트/리렌더링을 줄이고
- 번들 크기를 줄이고
- CDN/캐시/압축으로 네트워크를 줄이고
- 모니터링으로 계속 지켜본다.
이 흐름만 몸에 익혀두면,
어떤 규모의 서비스든 “느려지면 어디부터 손대야 할지”가 자연스럽게 떠오르게 될 거야.
'frontend > javascript' 카테고리의 다른 글
| 🟨 2-41. 프론트엔드 성능 아키텍처 완성 — ‘빠름’을 구조로 설계하기 (0) | 2025.11.07 |
|---|---|
| 🟨 2-40. 프론트엔드 성능 트러블슈팅 실전 — 느려질 때 어디부터 손대야 하는가 (0) | 2025.11.07 |
| 🟨 2-38. 이미지·폰트·애니메이션 최적화 — 시각적 성능 튜닝의 모든 것 (0) | 2025.11.07 |
| 🟨 2-37. React 앱 구조 최적화 — Code Splitting, Dynamic Import, Bundle 분석 완전 가이드 (0) | 2025.11.07 |
| 🟨 2-36. React 성능 병목 진단법 — Re-render, Virtual DOM, Memoization 완전 정리 (0) | 2025.11.07 |