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