본문 바로가기
웹 프로그래밍/Next.js

[유데미 | winterlood 이정환] 프로젝트로 배우는 React.js & Next.js 마스터리 클래스 강의 내용 정리 (섹션 15 ~ 16: Next.js 렌더링 2 - SSG, Next.js 렌더링 3 - ISR)

by 청량리 물냉면 2024. 1. 22.
반응형

섹션 15: Next.js 렌더링 2 - SSG 

SSG(정적 사이트 생성: Static Site Generation) 이해하기

SSR: 브라우저의 요청에 따라 실시간으로 서버 측에서 페이지를 렌더링 하는

SSG: 서버 측에서 페이지를 빌드 타임에 한 번만 렌더링 하는 것. 프로젝트 빌드 할 때 페이지가 미리 생성된다.

 

즉, 페이지 생성 시점에 따라 SSR, SSG의 차이가 나타난다.

 

SSG

  1. 프로젝트를 빌드할 때 html 페이지가 미리 생성된다.
  2. 이후 브라우저가 페이지를 요청하면, 미리 생성되어 있는 html 파일을 반환해 준다
  3. 렌더링이 완료된 페이지를 화면에 그려준다.

4. hydration 과정을 거쳐 응답이 가능한 웹페이지를 생성한다. (SSR과 동일)

 

 

장점

SSR에 비해 응답 속도가 굉장히 빠르다 

 

단점

빌드 타임 이후에는 페이지를 다시 생성하지 않으므로 언제나 똑같은 페이지만 응답하게 된다. 즉, 페이지의 최신 데이터를 반영하지 못한다는 단점도 존재한다.

 

정리

SSG는 빠른 페이지 응답을 보장할 수 있으나 최신 데이터를 반영하기 어렵기 때문에, 페이지 내부 데이터가 변경되지 않는 페이지에 적절한 렌더링 전략이다.

 

 

SSG(정적 사이트 생성) 적용하기 1

pages > index.js

// 해당 컴포넌트를 SSG 방식으로 동작하게 한다.
// 함수는 빌드 타임에만 실행된다.
export const getStaticProps = async () => {
  const countries = await fetchCountries();

  return {
    props: {
      countries,
    },
  };
};

 

빌드

npm run build
Route (pages)                              Size     First Load JS
┌ ● / (1945 ms)                            305 B          78.4 kB
├   /_app                                  0 B            78.1 kB
├ ○ /404                                   182 B          78.2 kB
├ λ /api/hello                             0 B            78.1 kB
├ λ /country/[code]                        476 B          78.5 kB
└ λ /search                                429 B          78.5 kB
+ First Load JS shared by all              78.3 kB
  ├ chunks/framework-5429a50ba5373c56.js   45.2 kB
  ├ chunks/main-fdf56fc4602be5bb.js        31.6 kB
  ├ chunks/pages/_app-8842e27c99faf60b.js  476 B
  ├ chunks/webpack-8fa1640cc84ba8fe.js     750 B
  └ css/d5d962fb1654e0ac.css               223 B

 

●: SSG 방식 페이지

○: getStaticProps 함수가 존재하지 않는 페이지, 기본적으로 SSG로 동작한다.

λ: SSR 방식 페이지

 

개발모드로 진입

npm run start

👉 인덱스 페이지 새로고침 시 매우 빠른 속도로 렌더링이 이루어짐을 알 수 있다.

 

 

SSG(정적 사이트 생성) 적용하기 2 (동적 경로 페이지 생성, fallback 옵션: false)

country/[code] 👉 국가코드 불러오는 페이지 변경.

불러오는 국가 데이터가 시간에 따라 변경되지 않으므로 SSG 처리

 

country > [code].js

import { fetchCountry } from "@/api";
import SubLayout from "@/components/SubLayout";
import { useRouter } from "next/router";

export default function Country({ country }) {
  const router = useRouter();
  // const code = router.query.code; //URL파라미터로 전달된 국가코드 꺼내오기
  const { code } = router.query; //구조분해 할당

  return (
    <div>
      {country.commonName} {country.officialName}
    </div>
  );
}

Country.Layout = SubLayout; //SubLayout을 적용시켜준다.

export const getStaticPaths = async () => {
  return {
    path: [{ params: { code: "ABW" } }, { params: { code: "KOR" } }],
    fallback: false, //path에 명시하지 않은 경로의 요청이 오면 404페이지 반환
  };
};

export const getStaticProps = async (context) => {
  const { code } = context.params;

  let country = null;
  if (code) {
    country = await fetchCountry(code);
  }

  return {
    props: { country },
  };
};

동적 경로를 갖는 페이지를 SSG 방식으로 동작시키고자 한다면, `getStaticProps` 외에  `getStaticPaths`가 추가로 필요하다. `getStaticPaths`는 어떤 경로를 갖는 페이지들을 빌드 타임에 정적 생성할 것인지 정의하는 함수이다.

`npm build` 해보면, country 폴더 밑에 정의한 국가코드의 페이지가 개별적으로 만들어진 것을 확인할 수 있다.

 

 

SSG(정적 사이트 생성) 적용하기 3 (동적 경로 페이지 생성, fallback 옵션: "blocking", true)

 

fallback: "blocking"

실시간으로 html을 생성하여 브라우저에 렌더링 시켜주고, 생성된 html을 저장한다.

 

country > [code].js

import { fetchCountry } from "@/api";
import SubLayout from "@/components/SubLayout";
import { useRouter } from "next/router";

export default function Country({ country }) {
  const router = useRouter();
  // const code = router.query.code; //URL파라미터로 전달된 국가코드 꺼내오기
  const { code } = router.query; //구조분해 할당

  // 혹시 api를 통해 데이터를 불러오는 것이 실패했을 때를 대비한 예외처리
  if (!country) {
    return <div>존재하지 않는 국가입니다.</div>;
  }

  return (
    <div>
      {country.commonName} {country.officialName}
    </div>
  );
}

Country.Layout = SubLayout; //SubLayout을 적용시켜준다.

export const getStaticPaths = async () => {
  return {
    paths: [{ params: { code: "ABW" } }, { params: { code: "KOR" } }],
    fallback: "blocking", //path에 명시하지 않은 경로의 요청이 오면 새 페이지 생성 및 저장
  };
};

export const getStaticProps = async (context) => {
  const { code } = context.params;

  let country = null;
  if (code) {
    country = await fetchCountry(code);
  }

  return {
    props: { country },
  };
};

 

 

fallback: true

`fallback: "blocking"`과 달리, 페이지 생성 시 걸리는 렌더링 시간을 더 줄일 수 있다.

우선 props가 없는 버전의 빈 html을 생성하여 전송해 주고 서버에서는 백그라운드에서 props를 따로 계산하여 계산 완료된 props값을 브라우저에 전송해 준다.

 

country > [code].js

import { fetchCountry } from "@/api";
import SubLayout from "@/components/SubLayout";
import { useRouter } from "next/router";

export default function Country({ country }) {
  const router = useRouter();
  // const code = router.query.code; //URL파라미터로 전달된 국가코드 꺼내오기
  const { code } = router.query; //구조분해 할당

  // props를 기다리는 상태
  if (router.isFallback) {
    return <div>Loading...</div>;
  }

  // 혹시 api를 통해 데이터를 불러오는 것이 실패했을 때를 대비한 예외처리
  if (!country) {
    return <div>존재하지 않는 국가입니다.</div>;
  }

  return (
    <div>
      {country.commonName} {country.officialName}
    </div>
  );
}

Country.Layout = SubLayout; //SubLayout을 적용시켜준다.

export const getStaticPaths = async () => {
  return {
    path: [{ params: { code: "ABW" } }, { params: { code: "KOR" } }],
    fallback: true,
  };
};

export const getStaticProps = async (context) => {
  const { code } = context.params;

  let country = null;
  if (code) {
    country = await fetchCountry(code);
  }

  return {
    props: { country },
  };
};

 

👉 초기 렌더링 시 'Loading...'이 뜨고 이후에 값을 불러오면서 페이지 내부가 채워진다.

 

 

SSG(정적 사이트 생성) 적용하기 3 (동적 경로 페이지 생성, 쿼리 스트링 처리)

쿼리 스트링의 경우 존재하는 변수가 매우 많기 때문에, 다이나믹한 SSG 페이지 처리가 어렵다.

따라서 우선 빈 페이지가 나타나고 CSR 방식을 이용해 내부 데이터를 채우는 식으로 SSG와 유사하게 동작하도록 처리해 준다. 

 

search > index.js

import { fetchSearchResults } from "@/api";
import SubLayout from "@/components/SubLayout";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";

export default function Search() {
  const router = useRouter();
  const { q } = router.query; //쿼리 스트링 추출

  //불러온 데이터(국가 리스트)를 저장하는 리스트
  const [countries, setCountries] = useState([]);

  const setData = async () => {
    const data = await fetchSearchResults(q);
    setCountries(data);
  };

  // 컴포넌트 마운트 or 쿼리스트링 변화시에 실행
  useEffect(() => {
    if (q) {
      //쿼리스트링 존재시에만 리스트를 불러오기
      setData();
    }
  }, [q]);

  return (
    <div>
      {countries.map((country) => (
        <div key={country.code}>{country.commonName}</div>
      ))}
    </div>
  );
}

Search.Layout = SubLayout;

👉 getStaticProps, getServerSideProps가 없는 컴포넌트는 기본적으로 SSG 방식으로 동작.

👉 초기 렌더링 시 빈 페이지가 뜨고 CSR 방식으로 데이터가 채워지며 원하는 결과가 나타난다.

 

 

섹션 16: Next.js 렌더링 3 - ISR

ISR (증분 정적 재생성) 이해하기

  • 일정 시간을 주기로 정적 페이지를 다시 생성하는 기술
  • SSG로 동작하는 페이지는 페이지의 업데이트 없이 정해진 페이지만 브라우저에 전송해줌 👉 ISR을 사용하면, 일정 기간이 지나면 서버 측에서 페이지를 재생성하도록 해서 최신 데이터를 반영하도록 해줄 수 있다.
  • 특정 시간까지는 이미 만들어져 있는 페이지를 반환하므로 매우 빠른 속도로 렌더링 가능 👉 SSG의 장점
  • 주기적으로 페이지를 업데이트 해 최신 데이터를 반영함  👉 SSR의 장점

country > [code].js

...
export default function Country({ country }) {
  ...
export const getStaticProps = async (context) => {
  
  ...
  return {
    props: { country },
    revalidate: 3,//3초 주기로 페이지 재생성
  };
};

`revalidate` 속성을 이용하여 간단히 처리해 줄 수 있다.

 

 

정리

  • 빠른 사용자 응답이 필요할 때(일단 페이지 보여주기)  👉 SSG
  • 최신 데이터 반영  👉  ISR
  • 실시간 데이터가 반드시 보장되어야 할 때  👉 SSR 이용 또는 CSR 이용해 클라이언트에서 fetch해오기

 

 

 

출처

https://kmooc.udemy.com/course/react-next-master/learn/lecture/39610746

반응형