Ch 06. 언제 어떤 방식을 쓸까 — 의사결정 가이드
정리가 필요한 시점
SSR, CSR, SSG, ISR — 네 가지 방식을 모두 배웠습니다. 이제 실무에서 마주치는 가장 중요한 질문이 남았습니다. "이 페이지에는 어떤 방식을 써야 할까?"
이 챕터에서는 네 가지 방식을 한눈에 비교하고, 결정하는 데 도움이 되는 흐름을 정리합니다.
네 가지 방식 비교
| 구분 | SSG | ISR | SSR | CSR |
|---|---|---|---|---|
| HTML 생성 시점 | 빌드 | 빌드 + 주기적 재생성 | 요청마다 | 브라우저에서 |
| 데이터 신선도 | 배포 시점 | 설정 주기 | 항상 최신 | 요청 시점 |
| 서버 부하 | 없음 | 매우 낮음 | 높음 | 없음 |
| SEO | 최상 | 최상 | 우수 | 어려움 |
| 초기 로딩 속도 | 최상 | 최상 | 우수 | 느림 |
| 실시간 상호작용 | 불가 | 불가 | 불가 | 가능 |
| 사용자 맞춤화 | 어려움 | 어려움 | 가능 | 가능 |
의사결정 흐름
실무에서 렌더링 방식을 선택할 때 다음 순서로 생각합니다.
[1] 이 페이지에 사용자 상호작용이 핵심인가? (클릭, 입력, 드래그, 실시간 업데이트) | YES → CSR ("use client" + useEffect) NO → [2]로 이동 |[2] 이 페이지에 로그인 사용자 전용 데이터가 있는가? (내 주문 내역, 내 설정, 세션 기반 데이터) | YES → SSR (cache: 'no-store' 또는 dynamic = 'force-dynamic') NO → [3]으로 이동 |[3] 데이터가 얼마나 자주 바뀌는가? | 거의 안 바뀜 (회사 소개, 약관, 문서) → SSG (force-cache 또는 기본값) 가끔 바뀜 (블로그 목록, 제품 정보) → ISR (revalidate: N초) 자주 바뀜 (뉴스, 주식 시세, 검색) → SSR
실무 예시
구체적인 예시로 확인해봅니다.
블로그 글 목록 → ISR
블로그 글은 매일 몇 개씩 추가됩니다. 항상 최신일 필요는 없지만, 하루 전 글이 없는 건 이상합니다. revalidate: 3600으로 1시간마다 갱신하면 충분합니다.
// 파일: app/blog/page.tsx
export const revalidate = 3600;
export default async function BlogListPage() {
const posts = await fetch('https://api.example.com/posts').then((r) =>
r.json()
);
// ...
}
```text
**댓글 목록 → SSR**
댓글은 방금 달린 것이 바로 보여야 합니다. 사용자가 댓글을 달고 새로 고침했는데 자신의 댓글이 안 보이면 황당하겠죠. `cache: 'no-store'`로 매 요청마다 최신 댓글을 가져옵니다.
```tsx
// 파일: app/posts/[slug]/page.tsx
async function getComments(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}/comments`, {
cache: 'no-store',
});
return res.json();
}
```text
**대시보드 → CSR**
실시간으로 바뀌는 차트, 드래그로 순서를 바꾸는 위젯, 버튼으로 필터를 바꾸는 테이블. 이런 UI는 CSR이 맞습니다. 브라우저에서 상태를 관리하고 API를 직접 호출합니다.
```tsx
// 파일: app/dashboard/components/MetricsChart.tsx
'use client';
import { useState, useEffect } from 'react';
export default function MetricsChart() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/metrics').then((r) => r.json()).then(setData);
const id = setInterval(() => {
fetch('/api/metrics').then((r) => r.json()).then(setData);
}, 5000);
return () => clearInterval(id);
}, []);
// ...
}
```text
**회사 소개 페이지 → SSG**
회사 소개, 이용약관, 개인정보처리방침 같은 페이지는 거의 변하지 않습니다. SSG로 한 번 만들어두면 충분합니다. CDN에서 전 세계 어디서나 빠르게 제공됩니다.
```tsx
// 파일: app/about/page.tsx
// 캐시 옵션 없음 = 기본값 force-cache = SSG처럼 동작
export default async function AboutPage() {
const info = await fetch('https://api.example.com/company-info').then((r) =>
r.json()
);
// ...
}
한 페이지 안에서 혼합하기
실제 페이지는 하나의 방식만 쓰지 않아도 됩니다. 예를 들어 블로그 게시글 상세 페이지에서, 게시글 본문은 SSG로 미리 생성하고, 댓글 목록은 CSR로 클라이언트에서 불러오는 방식을 함께 쓸 수 있습니다.
Next.js의 Server Component와 Client Component를 조합하면 이런 혼합이 자연스럽습니다. Server Component가 본문을 SSG/SSR로 렌더링하고, 그 안에 있는 Client Component가 댓글을 CSR로 처리합니다.
이 파트를 마치며
렌더링 방식은 성능, SEO, 서버 비용, 사용자 경험 모두에 영향을 줍니다. 처음부터 완벽한 선택을 하지 않아도 됩니다. Next.js는 언제든 방식을 바꾸기 쉽게 설계되어 있습니다. 의심스러우면 SSR로 시작하고, 성능 요구사항이 생기면 SSG나 ISR로 전환하면 됩니다.
다음 파트에서는 렌더링 이야기를 잠시 내려놓고, 페이지와 URL을 어떻게 구성하는지 알아봅니다. App Router의 파일 기반 라우팅 세계로 들어갑니다.