학습 Next.js - Day 09 / SSG (정적 사이트 생성), 실습

이유승·2024년 10월 8일

Next.js 학습

목록 보기
10/27
post-thumbnail



1. SSG (정적 사이트 생성)

  • SSG의 기본 개념은 예전에 작성한 포스트를 참조.

  • Next.js에서 제공하는 사전 렌더링 방법 중 2번째 방법.

  • SSR의 방식에서 발생하는 단점을 (데이터 페칭이 늦어질 경우 뒤에 위치한 모든 과정들이 덩달아 지연) 해결할 수 있다.

  • 서버에서 최초로 1번. 빌드 과정 중에 모든 작업을 수행해서 렌더링까지 완료시켜둔다. 빌드 중에 데이터 페칭을 포함한 모든 작업들이 수행되기 때문에, 사용자가 페이지를 요청한 시점에는 요청을 빠르게 처리할 수 있다.

  • 다만, 이미 완성된 페이지를 반환하기만 하기 때문에 데이터가 변화하거나 했을 때의 작업을 처리할 수가 없다.



2. SSG (정적 사이트 생성) 실습 1

SSG 방식을 적용하는 방법

  • getServerSideProps 함수의 사용으로 SSR을 사용했던 것 처럼, SSG를 사용하기 위해서는 getStaticProps 함수를 사용하면 된다.
export const getStaticProps = async () => {
  const [allBooks, recoBooks] = await Promise.all([
    fetchBooks(),
    fetchRandomBooks(),
  ]);

  return {
    props: {
      allBooks,
      recoBooks,
    },
  };
};
  • getServerSideProps 대신에 getStaticProps을 사용한다는 것 이외에 기본 구조는 동일하다.
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>
  );
}
  • 페이지 컴포넌트도 비슷하다. InferGetServerSidePropsType 타입 대신에 InferGetStaticPropsType 타입을 사용한다는 것 정도만 다르다.

  • 개발모드에서는 SSG 방식이라고 해도 요청이 들어올 때마다 사전 렌더링을 매번 새롭게 실행한다. SSG의 정상 동작 확인을 위해서는 프로젝트를 실행할 때, 프로덕션 모드로 실행해줘야 한다.

  • SSR, SSG가 혼합되어 있을 때. 프로젝트를 빌드해보면 Next.js에서 어느 파일이 어떤 방식으로 사전 렌더링을 실행하는지 구분해서 보여준다. 개발 시 참고.

  • Dynamic? 브라우저에서 요청을 받을 때마다 다이나믹하게 사전 렌더링이 실행된다. SSR 방식. api Route의 경우 Next.js가 기본적으로 SSR 방식으로 지정해준다.

  • Static? SSG 방식으로 동작하는데, 개발자가 getServerSideProps나 getStaticProps 함수를 설정하지 않을 경우 Next.js에서 기본적으로 SSG 방식으로 적용시켜둔다. 개발자가 명시적으로 설정을 하지 않았으니 구분은 따로 이루어지는 것.

export const getStaticProps = async (
  context: GetStaticPropsContext
) => {
  const q = context.query.q;
  const books = await fetchBooks(q as string);

  return {
    props: {
      books, 
    },
  };
};
  • 당연하지만, SSG 방식을 사용한다면 context 객체를 통해서 쿼리 스트링이나 param 값을 가져올 수가 없다. SSG는 빌드 타임 중에 딱 1번 모든 작업을 수행하고, 빌드 타임 중에는 쿼리 스트링과 같은 값을 가져올 방법이 없기 때문. 검색 결과를 서버에서 불러온다는 등의 작업 등은 아예 수행이 불가능하다. SSG의 단점.

  • SSG에서 이런 작업을 수행하고 싶다면, React.js에서 사용했던 방법을 쓰면 된다.

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>
  );
}
  • 이렇게 되면 페이지 컴포넌트는 기본적으로 SSG 방식으로 동작하긴 하지만, HTML 같은 정적 요소만 사전 렌더링 하고 나머지는 CSR 방식으로 동작하게 된다. React.js의 CSR 방식을 Next.js에서 적절히 적용하는 셈.



3. SSG (정적 사이트 생성) 실습 2

동적 경로에 SSG를 적용하는 방법

  • 우선 일반 경로 방식과 동일하게 getServerSideProps 대신에 getStaticProps을, InferGetServerSidePropsType 타입 대신에 InferGetStaticPropsType 타입을 적용해준다.
export const getStaticProps = async (
  context: GetStaticPropsContext
) => {
  const id = context.params!.id;
  const book = await fetchOneBook(Number(id));

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

export default function Page({
  book,
}: InferGetStaticPropsType<typeof getStaticProps>) {
  if (!book) return "문제가 발생했습니다 다시 시도하세요";
}

  • 다만 동적 경로 방식에서는 이외에도 getStaticPaths라는 함수를 추가로 구현해주어야 한다. id 페이지를 예시로 들자면, id 값은 변화하는 값이고. 변화하는 값은 SSG 방식에서 사용할 수 없는 형식이기 때문.

  • 따라서 동적 경로 방식에서 SSG를 사용하기 위해서는, 이 페이지에서 어떤 경로들이 존재하는지 모든 경우의 수를 작성해주어야 한다.

export const getStaticPaths = () => {
  return {
    paths: [
      { params: { id: "1" } },
      { params: { id: "2" } },
      { params: { id: "3" } },
    ],
    fallback: false,
  };
};
  • 서버를 통해 데이터베이스에 필요한 값을 모두 받아오던지, 아니면 페이지 컴포넌트 내부에서 필요한 값들을 작성해주면 된다.

  • getStaticPaths 함수는 이 페이지에서 생성할 수 있는 모든 경로들을 설정해준다. 그리고 getStaticProps는 이를 이용해서 생성 가능한 모든 페이지들을 사전에 렌더링한다.

  • getStaticPaths 함수 내부의 params의 값들은 반드시 문자열 형식으로 입력해주어야 한다.

  • fallback? 여기서는 getStaticPaths에서 명시하지 않은 경로로 요청이 들어왔을 때, 어떻게 처리할 것인지를 규정하는 예외 처리를 적용하는 용도로 사용된다.
    -> false : 404 페이지로 이동된다.
    -> blocking : SSR 방식을 적용하여, Next 서버에서 즉석으로 사전 렌더링으로 페이지를 생성한다.
    -> true : SSR 방식이지만, 데이터가 존재하지 없는 레이아웃만 우선 반환한다.

  • fallback 옵션의 경우, false와 true 이외의 옵션은 문자열 형식으로 기재해줘야 한다.

  • blocking 방식의 경우, 한번 생성된 페이지들은 Next 서버에 저장이 된다. 새로고침을 하더라도 새로 생성하거나 하지 않는다는 것. SSR으로 생성해서 SSG로 동작한다 정도로 이해하면 좋다.

  • 빌드 타임에 모든 데이터를 다 불러오기가 부담스럽거나, 새로운 데이터가 추가되어야 하는 상황이라면 blocking 방식을 사용해서 SSR + SSG의 혼합 적용을 염두해볼 수 있다.

  • 그런데, SSR 방식의 사전 렌더링은 추가적인 데이터 요청으로 시간이 지연될 수 있다. 이 경우에는 fallback 옵션을 true로 설정해볼 수 있다.

  • true에서는 우선 데이터 페칭을 하지 않고 props가 없는 페이지를 사용자에게 반환해준다. 데이터가 없는 레이아웃만 보내기 때문에, 사용자에게 화면을 우선 빠르게 보여주고 데이터는 후속으로 처리한다.

데이터의 로딩 중 상태와 로딩 실패 상태를 구분하기.

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

  if (router.isFallback) return "로딩중입니다";
  if (!book) return "문제가 발생했습니다 다시 시도하세요";
  
  if (!book) {
    return {
      notFound: true,
    };
  }

}
  • fallback이라는 단어는 페이지 컴포넌트가 서버로부터 아직 데이터를 전달받지 못한 상태를 뜻하기도 한다.

  • 페이지를 정상적으로 렌더링 하기 위해서는 데이터가 모두 받아와져야 한다. 그런데 그 사이에 시간적인 지연이 발생한다면? 사용자에게 데이터를 불러오고 있다는 사실을 명시적으로 보여주어야 한다.

  • 그런데 데이터의 존재 유무만을 가지고 로딩 화면의 렌더링 여부를 결정하기에는, 진짜 문제가 발생해서 데이터가 반환되지 않았을 경우를 구분해서 처리할 수가 없다.

  • 이를 위해서 router 객체의 isFallback 상태를 사용할 수 있다. 데이터의 로딩 중, 그리고 로딩 실패 상황을 구분할 수 있는 것.

  • 로딩 실패 상황을 조금 더 세련되게 처리하고 싶다면, return 키워드와 notFound 옵션을 사용하면 된다. 이 경우 페이지가 404 페이지로 이동되게 된다.









00. 강의 소개.

profile
프론트엔드 개발자를 준비하고 있습니다.

0개의 댓글