Section 3. Page Router 핵심 정리(6)

OlMinJe·2025년 9월 30일

Next.js

목록 보기
7/20
post-thumbnail

인프런 "한 입 크기로 잘라먹는 Next.js" 수강

1. SSG란?

정적 사이트 생성(SSG)은 SSR의 단점을 보완하는 사전 렌더링 방식이다.
즉, 빌드 타임에 페이지를 미리 생성해두는 것!!

서버가 실행되기 전에 모든 페이지를 미리 만들어두는 방식이라, 사용자가 접속하면 서버는 그냥 미리 준비된 HTML을 "빠르게" 전달해준다.
이 방식은 빌드 시간이 오래 걸려도 개발자만 겪는 고통이라, 사용자 입장에서는 항상 빠르게 응답받을 수 있다.


SSG의 장점 & 단점

장점

  • 빠른 속도: 페이지를 미리 만들어두니 요청 즉시 응답이 가능하다.
  • 안정성: 매번 서버 연산이 없어도 동일한 결과를 제공한다

단점

데이터 최신화가 어렵다.

  • 빌드 이후에는 페이지가 고정돼버려서, 새로운 요청이 와도 항상 같은 응답만 전달된다.
  • 문제가 되는건 아니지만, 최신 데이터를 반영하기 힘들다는게 아쉬운 점!

2. SSG 적용해보기

이전 게시글에서 다룬 getServerSideProps와 동일한 방식으로, SSG 전용 함수인 getStaticPropsgetStaticPaths를 사용하면 된다.


getStaticProps

빌드 타임에 필요한 데이터를 미리 불러와 페이지에 props로 전달해준다.

//...
import { InferGetServerSidePropsType } from 'next';
import { ReactNode } from 'react';

export const getStaticProps = async () => {
  const [allBooks, randomBooks] = await Promise.all([fetchBooks(), fetchRandomBooks()]);
  return {
    props: {
      allBooks,
      randomBooks,
    },
  };
};

export default function Home({
  allBooks,
  randomBooks,
}: InferGetStaticPropsType<typeof getStaticProps>) {
//...

여기에서 InferGetStaticPropsType의 역할은 InferGetServerSidePropsType와 동일하게 자동으로 타입을 추론해서 설정해준다.


빌드 타임에 딱 한 번 실행되는지 확인하기

export const getStaticProps = async () => {
  console.log('인덱스 페이지');
  const [allBooks, randomBooks] = await Promise.all([fetchBooks(), fetchRandomBooks()]);
  return {
    props: {
      allBooks,
      randomBooks,
    },
  };
};

해당 페이지를 새로고침 하면, 새로고침 할때마다 콘솔에 작성한 내용이 출력된다.

😎: 어라 한 번만 실행되야 하는거 아닌가요?
✍️: 개발 모드로 실행할 때는 수정 결과가 바로 반영되기 때문에, 렌더링 방식이 달라도 요청이 달라도 계속 사전 렌더링을 진행합니당! 그러니 Production(npm run build)로 실행해야 해용!

SSG 설정을 적용한 뒤 npm run build를 실행하여 콘솔 확인해보기

npm run build로 역할 알아보기

  • Generating static pages 👉 getStaticProps를 사용한 페이지들이 실제 정적 파일로 생성되는 걸 확인할 수 있음.
  • 이때 “인덱스 페이지” 같은 콘솔 메시지가 보였다면, 작성했던 함수(getStaticProps)가 빌드 타임에 실행된 게 맞다는 증거!

● ○ f 심볼의 의미

빌드 로그 맨 아래를 보면 페이지마다 ●, ○, f 표시가 붙어 있는데, 이건 해당 페이지가 어떤 방식으로 사전 렌더링되었는지 알려주는 표식이다.

● (Static)

  • 그냥 정적 페이지. getStaticProps 없이도 기본적으로 제공되는 정적 페이지.

○ (SSG)

  • getStaticProps를 사용해 SSG로 생성된 페이지.
  • 빌드 타임에 데이터 패칭까지 끝내고 정적으로 만들어진 페이지.

ƒ (Dynamic)

  • 동적으로 서버 사이드 렌더링(SSR)되는 페이지.
    -주문형(On demand)으로, 브라우저 요청이 들어올 때마다 페이지가 새로 사전 렌더링 됨.

API Routes의 기본 동작

여기에서 API Routes는 자동으로 SSG 동작한다. 즉, API 라우트에 대해서는 매 요청마다 서버에서 새롭게 실행된다는 점!


Production 모드에서 확인하기 🚀

npm run buildnpm run start로 production 모드를 실행하면, SSG, SSR로 설정한 기본 정적 페이지들이 각각 어떤 방식으로 동작하는지 브라우저에서 직접 확인할 수 있다.


🚨 SSG에서 Query String은 왜 못 쓸까?

  • Query String(?q=검색어)이라는 건 브라우저가 요청을 보낼 때 URL에 붙여서 전달하는 값
  • 그런데 SSG의 getStaticProps는 빌드 타임에 실행된다.

빌드 타임에는 브라우저 요청이 아예 없으니query를 알 수가 없어서, GetStaticPathsContext 타입에 query가 아예 정의되어 있지 않음

이를 해결하기 위해서는, 빌드 타임에서 query 값을 알아야 하는데 이건 불가능하다.

😎: 정적인 페이지를 생성할 때 query string을 활용할 수 없으면 어떻게 동작시켜요?
✍️: 사전 렌더링이 끝난 뒤, 클라이언트 측에서 직접 query를 읽고 처리해야 한다. (아래의 방식처럼 해결하면 된다!)

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

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

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

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

//...
  1. SSG로 기본 HTML만 내려주고,
  2. 브라우저가 켜진 뒤 useRouter 훅을 이용해 router.query에서 값을 꺼내와서
  3. 렌더링하는 식으로 처리

데이터 관련 부분을 제외하고 나머지 부분만 렌더링해서 브라우저에게 보내주는 걸 확인할 수 있으며,

결과 확인

사진 속 결과처럼 useEffect를 통해서 서버에게 데이터를 직접 요청하는 방식인 SSR으로 동작하는 걸 확인할 수 있다.


getStaticPaths

동적 라우팅 페이지(/posts/[id] 같은)에서 어떤 경로를 미리 빌드할지 정해줄 수 있다.

//...
import type { GetStaticPathsContext, InferGetStaticPropsType } from 'next';
import { ReactNode } from 'react';

export const getStaticProps = async (context: GetStaticPathsContext) => {
  const q = context.query.q as string;
  const books = await fetchBooks(q);
  return {
    props: {
      books,
    },
  };
};

export default function Page({ books }: InferGetStaticPropsType<typeof getStaticProps>) {

이때 같은 방식으로 적용하고 실행하면 아래와 같은 오류가 발생한다.

Error: getStaticPaths is required for dynamic SSG pages 
and is missing for '/book/[id]'.

이는 SSG 방식이 "어떤 경로들을 빌드 타임에 생성할지"를 미리 알아야 하는데, getSTataicPaths가 없으니 Next.js가 어떤 페이지를 만들어야 하는지 몰라서 발생한 오류이다.

✅ 해결 방법: getStaticPaths 작성

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

getStaticPaths 과정

  • paths: 빌드 타임에 미리 렌더링할 경로들을 배열로 작성(여기서는 /book/1 ~ /book/3)
  • params: URL 파라미터를 객체 형태로 지정 (⚠️파라미터 값은 꼭 문자열로!)
  • fallback: 설정하지 않은 경로로 접근했을 때 어떻게 처리할지 결정

getStaticPaths 빌드 결과

👉 이렇게 작성하면 /book/1 ~ /book/6은 빌드 타임에 정적 페이지로 만들어지고, 그 외 경로는 404가 뜨게 된다.


fallback 옵션

위에서 사용한 fallback 옵션을 false 이외에도 유연하게 설정할 수 있다.

fallback: false

  • 설정하지 않은 경로 = 404 Not Found
  • 아예 빌드 타임에 지정한 path만 접근 가능

fallback: "blocking"

fallback의 blocking

  • 지정하는 않은 경로에 접근하면, 서버에서 해당 페이지를 즉시 생성
  • 생성이 완료된 후에야 브라우저에 전달됨
  • SSR처럼 동작하지만, 생성된 결과는 캐싱되어 이후에는 정적으로 제공한다.

fallback의 blocking 실행 결과

⚠️ 주의할 점은
사전 렌더링 하는 시간이 길어지는 경우 브라우저에서 오랜 시간 기다려야 하는 상황이 발생할 수 있다. 이럴 때는 true 로 설정하면 된다.

fallback: true

fallback의 true 과정

  • 지정하지 않은 경로 접근하는 경우, 우선 “빈 페이지”를 보여주고
  • 백그라운드에서 데이터를 불러와 페이지를 완성한 뒤 교체 (Hydration 느낌!)
  • 즉시 응답이 가능하지만, 초기엔 “로딩 중” 같은 처리가 필요

fallback의 true 실행 결과

🔥 예외처리을 더 개선해자!

//...
export const getStaticProps = async (context: GetStaticPropsContext) => {
  const id = context.params!.id;
  const book = await fetchOneBook(Number(id));

  if (!book) {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      book,
    },
  };
};

export default function Page({ book }: InferGetStaticPropsType<typeof getStaticProps>) {
  const router = useRouter();

  if (router.isFallback) return '로딩 중입니다.';

  const { title, subTitle, description, author, publisher, coverImgUrl } = book;

  return (
 //...
  • useRouter의 속성 isFallback으로 fallback 상태를 확인하여, 로딩 중404를 구분해 사용자에게 보여줄 수 있다.
    로딩 중 출력

  • 404인 경우에 Not Found로 이동할 수 있게 noFound 속성을 true로 설정해준다.
    (존재하지 않은 페이지에 접근하면 이래의 이미지의 결과를 확인할 수 있다.)
    404 출력
profile
큐트걸

0개의 댓글