개발자공부일기
SSR/CSR/SSG/ISR 본문
1) SSR (Server-Side Rendering)
서버가 매 요청마다 HTML을 생성해 응답하는 방식. 사용자는 첫 화면을 HTML로 즉시 받고, 이후 브라우저가 JS를 받아 하이드레이션(Hydration)을 수행.
요청 흐름
- 클라이언트가 /page 요청
- 서버가 데이터 fetch → HTML 렌더링
- HTML 전달(콘텐츠 보임) → JS 로드 → 하이드레이션
장점
- SEO 최상: 첫 응답에 실제 콘텐츠 포함
- 개인화/권한 기반 UI에 유리: 쿠키·세션을 보고 매 요청마다 다르게 그림
- 실시간성 높은 페이지(주가, 대시보드)에 적합
단점
적합한 상황
- 로그인 대시보드, 장바구니/주문내역 등 사용자별 화면
- 크롤러에 중요한 상세 페이지(블로그도 가능하나 보통 SSG/ISR이 더 효율)
성능·SEO·캐시 포인트
- TTFB가 CSR/SSG보다 느릴 수 있음
- 개인화 시 캐시 키 분리(Vary: Cookie), 서버 캐시 계층 고려
- Edge(지역 POP)에서 SSR하면 네트워크 지연 감소 (Edge Runtime)
Next.js 예시
// app/dashboard/page.tsx
export const dynamic = 'force-dynamic' // 매 요청 SSR
// 또는 fetch(..., { cache: 'no-store' })
import { cookies } from 'next/headers'
export default async function Page() {
const user = cookies().get('session')?.value
const data = await fetch(process.env.API_URL + '/me', { cache: 'no-store' }).then(r => r.json())
return <main>안녕하세요, {data.name}님</main>
}
2) CSR (Client-Side Rendering)
초기에 최소한의 HTML+빈 컨테이너를 내려주고, 브라우저가 JS를 실행하여 데이터 fetch 후 화면 구성. SPA에서 많이 사용. 초기 체감 개선을 위해 프리패치나 Skeleton UI를 쓴다.
요청 흐름
- 브라우저가 빈 HTML과 번들 JS 수신
- JS 실행 → API 호출 → 데이터 수신
- 화면 렌더링
장점
- 서버는 정적 파일 서빙만 하면 됨 → 비용/스케일 단순
- 라우팅/상태관리/상호작용 중심 SPA에 적합
- 정적 자산 중심이라 CDN 캐시 용이
단점
적합한 상황
- 내부 도구, 관리자 페이지 등 앱형 UX
- SEO 중요도가 낮고 상호작용이 많음
성능·SEO·캐시 포인트
- 코드 스플리팅·지연 로딩·RSC 활용해 번들 크기 축소
- 프리패치, Skeleton UI로 체감 개선
- 검색 노출이 중요하면 SSR/SSG/ISR 고려
Next.js 예시
// app/client-page/page.tsx
'use client'
import { useEffect, useState } from 'react'
export default function ClientPage() {
const [data, setData] = useState<any>(null)
useEffect(() => {
fetch('/api/items').then(r => r.json()).then(setData)
}, [])
if (!data) return <p>로딩중...</p>
return <ul>{data.map((v:any) => <li key={v.id}>{v.title}</li>)}</ul>
}
3) SSG (Static-Site Generation)
빌드 타임에 HTML을 미리 생성해 두고 요청 시 그대로 서빙. 데이터가 거의 변하지 않거나 변동 빈도가 낮은 페이지에서 최고의 효율.
요청 흐름
장점
단점
- 데이터가 바뀌면 재배포 필요
- 동적/개인화는 제한적(클라이언트 후처리 필요)
적합한 상황
- 블로그 글, 문서, 마케팅 랜딩
- 자주 바뀌지 않는 제품 상세/카테고리 목록
성능·SEO·캐시 포인트
- 체감 속도와 SEO 모두 최상급
- 캐시 무효화는 재빌드/재배포 파이프라인으로 처리
Next.js 예시
// app/blog/[slug]/page.tsx
export const dynamic = 'force-static' // 기본이 static
export const revalidate = false // 재검증 없음 = 순수 SSG
export async function generateStaticParams() {
const posts = await fetch(process.env.API_URL + '/posts').then(r => r.json())
return posts.map((p: any) => ({ slug: p.slug }))
}
export default async function Page({ params }: { params: { slug: string }}) {
const post = await fetch(process.env.API_URL + `/posts/${params.slug}`, { cache: 'force-cache' }).then(r => r.json())
return <article dangerouslySetInnerHTML={{ __html: post.html }} />
}
4) ISR (Incremental Static Regeneration)
SSG의 장점을 유지하면서, 지정한 주기마다 백그라운드에서 정적 페이지를 재생성. 첫 방문자는 이전 캐시를 보고, 새 HTML이 준비되면 이후 방문자는 최신본을 받음 (Revalidation).
요청 흐름
- 최초 빌드 시 정적 HTML 생성·배포
- 요청 도착 →
revalidate시간이 지났다면 - 사용자에겐 기존 캐시 응답
- 백그라운드에서 새 HTML 재생성
- 다음 요청부터는 새 HTML 제공
장점
- TTFB 빠름 + 자동 최신화
- 서버 부하와 신선성의 균형
- “조금 늦게 최신화 돼도 OK” 페이지에 최적
단점
- 엄밀한 실시간 일관성은 보장하지 않음(짧은 구간 구버전 노출 가능)
- 인증/개인화에는 그대로 쓰기 어려움(캐시 오염 주의)
적합한 상황
- 뉴스/블로그(분~시간 단위 업데이트)
- 가격표/목록(약간의 지연 허용)
성능·SEO·캐시 포인트
- SEO 좋고 트래픽 피크에도 안정적
revalidate주기 설계가 핵심(짧으면 서버 부하↑, 길면 신선도↓)
Next.js 예시
// app/products/[id]/page.tsx
export const revalidate = 60 // 60초마다 백그라운드 재생성(ISR)
export async function generateStaticParams() {
const ids = await fetch(process.env.API_URL + '/product-ids').then(r => r.json())
return ids.map((id: string) => ({ id }))
}
export default async function Page({ params }: { params: { id: string }}) {
const product = await fetch(process.env.API_URL + `/products/${params.id}`, {
next: { revalidate: 60 }, // ISR 재검증 힌트
}).then(r => r.json())
return <main><h1>{product.name}</h1><p>{product.price}원</p></main>
}
비교표
| 항목 | SSR | CSR | SSG | ISR |
| 초기 응답 | 서버 생성 HTML | 빈 컨테이너+JS | 미리 만든 HTML | 미리 만든 HTML |
| SEO | 매우 좋음 | 상대적으로 불리 | 매우 좋음 | 매우 좋음 |
| 신선도 | 매 요청 최신 | 클라 fetch 시점 | 빌드 시점 | 재검증 주기 기반 |
| 서버 부하 | 높음 | 낮음(정적) | 매우 낮음 | 낮음~중간 |
| 개인화 | 강력 | 클라 후처리 | 거의 불가 | 거의 불가 |
| 캐시/CDN | 까다로움 | 쉬움 | 최강 | 강함(주기 기반) |
용어사전
Hydration
서버가 만든 정적 HTML에 브라우저가 이벤트/상태를 연결해 상호작용을 가능하게 만드는 과정.
TTFB (Time To First Byte)
브라우저가 요청을 보낸 뒤 첫 바이트를 받기까지 걸린 시간. 서버 처리·네트워크 지연이 길면 커진다.
SEO (Search Engine Optimization)
검색 엔진 최적화. 첫 응답 HTML에 실제 콘텐츠가 포함될수록 유리.
CDN (Content Delivery Network)
전 세계에 분산된 캐시 네트워크. 정적 자산/정적 HTML을 가까운 곳에서 제공해 지연을 줄인다.
POP (Point of Presence)
CDN/엣지 네트워크의 지역 거점. 사용자와 가까운 POP에서 응답하면 지연이 줄어든다.
Edge Runtime
사용자와 가까운 엣지 위치에서 코드를 실행하는 런타임. SSR의 네트워크 지연을 줄여 TTFB 개선에 도움.
Vary 헤더
캐시 키를 어떤 요청 헤더로 구분할지 지정. 예: Vary: Cookie로 개인화별 캐시 분리.
Cache-Control
캐시 동작을 제어하는 응답 헤더. public/private, s-maxage, max-age, no-store 등으로 수명/범위를 설정.
FCP (First Contentful Paint)
화면에 첫 콘텐츠가 그려지는 시점. CSR에서 데이터/번들이 늦으면 FCP가 늦어진다.
RSC (React Server Components)
컴포넌트를 서버에서 실행하고 결과만 브라우저로 보내 번들 크기를 줄이고 초기 체감을 개선.
Skeleton UI
데이터 로딩 동안 자리 표시(뼈대 화면)를 보여 체감 속도를 개선하는 UI 패턴.
Revalidation
ISR에서 지정 주기마다 캐시된 정적 페이지를 백그라운드에서 재생성해 최신 상태로 갱신.