Skip to content

Static Site Generation (SSG) - Page Router

SSG는 빌드 시점에 서버가 HTML을 미리 만들어두고,
배포 이후에는 요청이 들어오면 이미 생성된 HTML을 그대로 응답하는 렌더링 방식이다.


getStaticProps

getStaticProps가 존재하면 해당 페이지는 SSG로 동작한다.

tsx
export const getStaticProps = async () => {
  const books = await fetchBooks();

  return {
    props: {
      books,
    },
  };
};
  • 빌드 시점에 한 번만 실행된다.
  • 브라우저에서는 실행되지 않는다.
  • 반환한 props는 페이지 컴포넌트에 전달된다.

SSG 페이지의 실행 흐름

  1. 빌드 시점
  • 서버에서 getStaticProps 실행
  • HTML 생성
  1. 배포 이후
  • 요청 시 이미 생성된 HTML을 그대로 응답
  • JS 로드 후 Hydration 수행

SSR과 달리 요청마다 서버 코드가 실행되지 않는다.

Next.js 빌드 결과에서는 각 페이지가 정적, SSG, 또는 동적 SSR로 분류되어 출력된다.


장점

  • 빌드 타임에 렌더링 비용이 들어가더라도, 사용자 요청에는 매우 빠르게 응답할 수 있다.
  • 서버 부하가 적고 캐싱/CDN과 궁합이 좋다.

단점

  • 항상 같은 HTML을 응답하기 때문에 데이터 변경이 바로 반영되지 않는다.
  • 최신 데이터가 필요하면 재빌드가 필요하다(→ 이 단점을 보완하는 방식이 ISR)

SSG에서 실제 API 데이터 페칭

SSG에서도 SSR처럼 서버에서 API를 호출해 데이터를 가져올 수 있다.
차이점은 요청 시점이 아니라 빌드 시점에 한 번만 실행된다는 것이다.

tsx
export const getStaticProps = async () => {
  console.log('인덱스 페이지 빌드 중');

  const [allBooks, recoBooks] = await Promise.all([
    fetchBooks(),
    fetchRandomBooks(),
  ]);

  return {
    props: {
      allBooks,
      recoBooks,
    },
  };
};

export default function Home({
  allBooks,
  recoBooks,
}: InferGetStaticPropsType<typeof getStaticProps>) {
  return (
    <div className={style.container}>
      <section>
        <h3>지금 추천하는 도서</h3>
        {recoBooks.map((book) => (
          <BookItem key={book.id} {...book} />
        ))}
      </section>
      <section>
        <h3>등록된 모든 도서</h3>
        {allBooks.map((book) => (
          <BookItem key={book.id} {...book} />
        ))}
      </section>
    </div>
  );
}
  • getStaticProps는 빌드 시점에 한 번만 실행된다.
  • 배포 후 서버는 이 함수를 다시 실행하지 않는다.
  • console.log는 빌드 로그에서만 확인할 수 있다.

컴포넌트 자체는 SSR, SSG, CSR 모두 동일하게 생길 수 있다.
차이는 데이터를 언제, 어디서 준비하느냐에 있다.


SSG의 한계: 빌드 타임에 확정할 수 없는 데이터

SSG는 빌드 시점에 데이터를 확정해야 한다.
따라서 요청 시점에 값이 결정되는 페이지에는 구조적으로 한계가 있다.

  • 검색 페이지 (?q=react처럼 쿼리에 따라 결과가 달라지는 경우)
  • 사용자 입력에 따라 결과가 달라지는 페이지
  • 로그인 사용자별로 다른 데이터를 보여주는 페이지

검색 페이지는 CSR로 처리해야 한다

검색 페이지는 ?q=... 값이 사용자 요청 시점에 결정되기 때문에, SSG로는 검색어를 미리 알 수 없다. 이런 경우 클라이언트에서 쿼리 스트링을 읽고 useEffect로 데이터를 가져오는 CSR 방식이 적합하다.

tsx
import SearchableLayout from '@/components/searchable-layout';
import { ReactNode, useEffect, useState } from 'react';
import BookItem from '@/components/book-items';
import fetchBooks from '@/lib/fetch-books';
import { useRouter } from 'next/router';
import { BookData } from '@/types';

export default function Page() {
  const [books, setBooks] = useState<BookData[]>([]);

  const router = useRouter();
  const q = router.query.q;

  const fetchSearchResult = async () => {
    const data = await fetchBooks(q as string);
    setBooks(data);
  };

  useEffect(() => {
    if (q) {
      fetchSearchResult();
    }
  }, [q]);

  return (
    <div>
      {books.map((book) => (
        <BookItem key={book.id} {...book} />
      ))}
    </div>
  );
}

Page.getLayout = (page: ReactNode) => {
  return <SearchableLayout>{page}</SearchableLayout>;
};

검색 페이지의 초기 HTML (CSR)

검색어는 브라우저에서 결정되므로, 서버에서 내려오는 HTML에는 검색 결과가 포함되지 않는다.

  • 빌드 타임에는 페이지의 기본 HTML만 생성된다.
  • 실제 검색 데이터는 브라우저에서 fetch되어 렌더링된다.

즉, 이 페이지는 화면 구조는 SSG로 제공되고, 검색 결과는 CSR로 처리된다.


SSG는 빌드 타임에 확정 가능한 화면을 빠르게 제공하는 데 적합하고,

확정할 수 없는 데이터는 SSR이나 CSR로 분리해 처리해야 한다.


동적 경로에 SSG 적용하기

getStaticPaths

getStaticPaths는 빌드 시점에 어떤 경로를 미리 생성할지 정의하는 함수이다.

tsx
export const getStaticPaths = () => {
  return {
    paths: [
      { params: { id: '1' } },
      { params: { id: '2' } },
      { params: { id: '3' } },
    ],

    fallback: false, // "blocking" / true
  };
};
  • paths: 빌드 타임에 생성할 경로 목록
  • fallback: paths에 없는 경로 요청 시 동작 방식

Fallback 옵션

fallback: false

  • paths에 정의되지 않은 경로는 모두 404 반환
  • 가장 단순하고 예측 가능한 방식

모든 페이지를 빌드 타임에 확정할 수 있을 때 사용한다.


fallback: true

paths에 정의되지 않은 경로가 요청되면 다음 흐름으로 동작한다.

  1. props 없는 fallback 페이지 먼저 렌더링
  2. 백그라운드에서 HTML 생성
  3. 생성 완료 후 실제 페이지로 교체

즉, 일단 페이지부터 보여주고 나중에 완성하는 방식이다.
이 경우 페이지 컴포넌트에서는 fallback 상태 처리가 필요하다.

tsx
const router = useRouter();

if (router.isFallback) return '로딩중입니다.';
  • router.isFallback은 현재 페이지가 fallback 상태인지 여부를 나타낸다.
  • fallback 상태란 페이지는 렌더링되었지만 props 데이터가 아직 없는 상태를 의미한다.

fallback: 'blocking'

paths에 없는 경로 요청 시,

  • 서버가 HTML 생성이 완료될 때까지 대기
  • 완성된 페이지를 한 번에 응답

사용자 입장에서는 SSR처럼 동작하지만, 생성된 페이지는 이후 정적으로 캐싱된다.
즉, 최초 요청만 SSR 느낌 → 이후에는 SSG