← 글 목록

notes

Next.js SSG (Static Site Generation)

Next.js SSG의 빌드 시 정적 생성·데이터 처리와 적용 기준 메모.

💡 SSG는 Static Site Generation의 약자로, 빌드 시점에 페이지 HTML을 미리 생성해 두고, 모든 사용자에게 동일한 정적 파일을 제공하는 방식입니다. CDN 캐싱으로 빠른 응답과 SEO에 유리하며, 데이터 변경 시에는 전체 재빌드가 필요합니다.

📑 목차

  • SSG vs ISR vs SSR 비교
  • SSG 동작 원리
  • Pages Router에서 SSG 사용하기
  • App Router에서 SSG 사용하기
  • 언제 SSG를 사용할까?
  • 플랫폼 지원 및 제한사항
  • 참고 문서

📊 SSG vs ISR vs SSR 비교

핵심 차이

  • SSG → 빌드 시 한 번 생성, CDN 캐싱으로 최고 성능. 데이터 변경 시 next build 필요
  • ISR → SSG + 주기적/수동 갱신으로 재빌드 없이 업데이트 가능
  • SSR → 매 요청마다 서버 렌더링, 항상 최신 데이터

SSG vs ISR 타임라인


⚙️ SSG 동작 원리

빌드 시점 생성

1. next build 실행 - getStaticProps / getStaticPaths가 실행됨 - 외부 API, DB 등에서 데이터 fetch - 각 경로별 HTML 파일 생성

2. 배포 후 - 생성된 HTML이 CDN에 캐시됨 - 사용자 요청 시 CDN에서 즉시 응답 (빠른 TTFB)

3. 데이터 변경 시 - 전체 사이트 재빌드 (next build) 필요 - ISR과 달리 주기적 자동 갱신 없음

데이터 없이 SSG

페이지가 외부 데이터를 사용하지 않으면, 별도 설정 없이 빌드 시 자동으로 정적 생성됩니다.

// pages/about.tsx
function About() {
  return <div>About</div>
}

export default About

📄 Pages Router에서 SSG 사용하기

기본 예시 (데이터 있음)

getStaticProps를 export하면 해당 페이지가 빌드 시 정적 생성됩니다.

// pages/blog.tsx
export default function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/posts')
  const posts = await res.json()

  return {
    props: { posts },
  }
}

동적 라우트: getStaticPaths + getStaticProps

경로가 외부 데이터에 의존할 때는 getStaticPaths로 사전 생성할 경로를 지정합니다.

// pages/posts/[id].tsx
export default function Post({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

export async function getStaticPaths() {
  const res = await fetch('https://api.example.com/posts')
  const posts = await res.json()

  const paths = posts.map((post) => ({
    params: { id: String(post.id) },
  }))

  return { paths, fallback: false }
}

export async function getStaticProps({ params }) {
  const res = await fetch(`https://api.example.com/posts/${params.id}`)
  const post = await res.json()

  return { props: { post } }
}

fallback 옵션

⚠️ fallback: false가 순수 SSG에 가장 가깝습니다. true / 'blocking'은 첫 방문 시 온디맨드 생성이 발생합니다.

📱 App Router에서 SSG 사용하기

App Router에서는 fetch가 기본적으로 캐시되며, 정적 렌더링이 기본 동작입니다.

1. 기본 (자동 정적 생성)

// app/blog/page.tsx
export default async function BlogPage() {
  const res = await fetch('https://api.example.com/posts')
  const posts = await res.json()

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
fetch는 기본적으로 캐시되므로, 빌드 시 한 번만 실행되고 결과가 정적으로 사용됩니다.

2. 동적 라우트

// app/posts/[id]/page.tsx
export default async function PostPage({ params }: { params: { id: string } }) {
  const res = await fetch(`https://api.example.com/posts/${params.id}`)
  const post = await res.json()

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

export async function generateStaticParams() {
  const res = await fetch('https://api.example.com/posts')
  const posts = await res.json()

  return posts.map((post) => ({ id: String(post.id) }))
}

3. 동적 렌더링 비활성화

// app/products/page.tsx
export const dynamic = 'force-static'

export default async function ProductsPage() {
  const products = await getProducts()
  return <ProductList products={products} />
}

🎯 언제 SSG를 사용할까?

적합한 경우

  • 블로그, 포트폴리오
  • 마케팅·랜딩 페이지
  • 도움말, 문서
  • 이커머스 상품 목록 (자주 바뀌지 않는 경우)

부적합한 경우

  • 매 요청마다 달라지는 데이터 (사용자별, 실시간)
  • 자주 변경되는 콘텐츠 (뉴스, 재고 등) → ISR 고려
  • 인증이 필요한 대시보드 → SSR 고려
“사용자 요청 이전에 이 페이지를 미리 렌더링할 수 있는가?” → 예라면 SSG를 선택하세요.

⚠️ 플랫폼 지원 및 제한사항

지원 환경

주의사항

  • getStaticProps는 요청 객체에 접근 불가 (쿼리, 헤더 등)
  • props는 클라이언트 초기 HTML에 포함되므로 민감 정보 전달 금지
  • 동적 라우트 사용 시 getStaticPaths / generateStaticParams 필수

📚 참고 문서