웹 페이지의 응답 속도를 높이는 가장 확실한 방법 중 하나는 요청이 오기 전에 HTML을 미리 만들어두는 것이다. Next.js의 Static Site Generation(SSG) 은 next build 시점에 페이지를 렌더링해 정적 파일로 저장하고, 이후 요청에는 서버 연산 없이 바로 파일을 반환한다. 콘텐츠가 자주 바뀌지 않는 블로그, 문서, 마케팅 페이지에 특히 잘 맞는 전략이다.
SSG는 빌드 타임에 모든 HTML 파일을 생성하는 렌더링 방식이다. 서버는 단순히 미리 만들어진 파일을 CDN에서 전달하기 때문에 TTFB(Time To First Byte)가 매우 짧다.
next build 실행
│
▼
┌─────────────────────┐
│ generateStaticParams│ ← 동적 경로 목록 수집
└────────┬────────────┘
│ [slug: 'a', 'b', 'c', ...]
▼
┌─────────────────────┐
│ 페이지 컴포넌트 실행 │ ← 데이터 fetch + React 렌더링
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ /out/a.html │ ← 정적 HTML 파일 저장
│ /out/b.html │
│ /out/c.html │
└─────────────────────┘
│
▼
CDN 배포
TTFB: 브라우저가 첫 번째 바이트를 수신하기까지 걸리는 시간. 낮을수록 체감 속도가 빠르다.
App Router에서 동적 경로([slug])를 SSG로 처리하려면 generateStaticParams를 내보내야 한다. 이 함수가 반환하는 파라미터 배열 각각에 대해 빌드 타임에 페이지가 생성된다.
// app/posts/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return posts.map((post: { slug: string }) => ({
slug: post.slug,
}));
}
export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(r => r.json());
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
);
}
generateStaticParams가 없는 동적 경로는 기본적으로 요청 시 서버에서 렌더링된다(SSR). 정적으로 고정하려면 반드시 이 함수를 선언해야 한다.
SSG가 적합한 경우와 그렇지 않은 경우를 구분하는 기준은 콘텐츠 갱신 주기다.
| 상황 | 권장 전략 |
|---|---|
| 콘텐츠가 거의 바뀌지 않음 (블로그, 문서) | SSG |
| 수 분~수 시간 주기로 업데이트 | ISR |
| 요청마다 최신 데이터 필요 | SSR |
| 사용자 맞춤 데이터 | CSR 또는 SSR |
ISR(Incremental Static Regeneration) 은 SSG의 확장이다. 정적 파일을 생성하되, 일정 시간이 지나면 백그라운드에서 페이지를 재생성해 캐시를 갱신한다.
// ISR: 60초마다 페이지 재검증
export const revalidate = 60;
ISR: 빌드 후에도 특정 주기로 정적 페이지를 재생성하는 방식. revalidate 값(초)으로 제어한다.
순수 SSG는 재배포 없이는 콘텐츠를 갱신할 수 없다. 갱신 주기가 있다면 ISR, 완전히 고정된 콘텐츠라면 SSG가 더 단순하고 예측 가능하다.
장점
단점
페이지 수가 수천 개를 넘기 시작하면 generateStaticParams에서 일부 경로만 빌드하고, 나머지는 dynamicParams = true(기본값)로 두어 첫 요청 시 생성하는 혼합 전략도 유효하다.