NextJs SSG, SSR, ISR, CSR

sy u·2022년 8월 10일
0

들어가기

이전에 작성했던 SEO에 대한 글에서 검색 엔진 최적화를 위해 할 수 있는 일 중 서버에서 HTML을 렌더링하여 클라이언트로 보내는 서버사이드렌더링을 사용하는 것이 있었다.

NextJs는 대표적인 서버사이드렌더링을 제공하는 프레임워크이다.
나는 이 글을 작성하며 NextJs가 서버에서 페이지에 필요한 데이터를 가져오고 그 데이터를 활용하여 HTML로 렌더링하는 것을 공부할 예정이다.

NextJs는 서버사이드렌더링만 제공하는 것이 아니다. Data Fetching에 대한 방법이 4가지나 존재한다. 우선 그 4가지 방법들에 대해 알아보자

NextJs Data Fetching

NextJs 에서 Data Fetching 방법은 SSG, SSR, ISR, CSR로 총 네가지가 있다.

🔎 SSG (Static Site Generation)

SSG는 빌드시간에만 페이지에 필요한 데이터를 가져오고 해당 데이터를 사용하여 HTML을 렌더링한다.

즉, 우리가 next build 명령어를 작성할 때만 페이지가 렌더링되는 것이다.
📌 하지만 개발모드인 경우에는 모든 페이지 request마다 페이지가 렌더링된다.

getStaticProps 함수로 SSG 방법 사용하기

페이지 파일에 getStaticProps라는 함수를 사용해 페이지를 빌드 시간(build time)에 사전 렌더링(pre-render)할 수 있다.

export async function getStaticProps(context) {
  return {
    props: {},
  }
}

🗺️ 함수를 사용할 수 있는 곳

오직 페이지 컴포넌트에서만 사용할 수 있으며 컴포넌트 내부가 아닌 컴포넌트 외부에 단독으로 사용해야한다.

⌚ 함수가 실행되는 시점

위에서 언급했듯이 SSG는 페이지를 build 시간에만 렌더링한다. 따라서 getStaticProps 함수 또한 build시에만 실행된다.

📄 정적 HTML, 정적 JSON 생성

getStaticProps는 빌드 시, 렌더링한 HTML 파일 뿐만 아니라 JSON 또한 생성한다.

생성된 JSON 파일은 이미 렌더링된 페이지로 이동할 때 페이지 컴포넌트의 props로 사용할 수 있다.
페이지 접근 시, 미리 생성된 JSON을 props로 사용할 뿐 getStaticProps가 호출되지 않는다.

또한 빌드 시 생성된 HTML 파일과 CDN 파일은 정적인 파일이기 때문에 CDN에 캐시하여 사용할 수 있다.

특징

  1. 페이지 요청 시, 함수가 호출되는 것이 아니기 때문에 들어오는 request(HTTP 헤더나 query 파라미터 등)에 접근할 수 없다.
  2. getStaticProps내부에 사용하는 모듈을 최상위 최상위 scope에 import할 수 있다.
    • 서버에서만 실행되기 때문에 import는 클라이언트 측에 번들되지 않는다.

😀 SSG 장점

빠르다.

페이지를 빌드 시간에 미리 렌더링하고 또 이렇게 사전에 렌더링된 페이지를 CDN에 캐시할 수 있기 때문에 페이지 로드가 빠르다.

SEO에 친화적이다.

사전에 렌더링하기 때문에 페이지 소스에서 컨텐츠를 볼 수 있기 때문에 크롤러가 우리 페이지를 크롤링할 때 많은 컨텐츠를 제공할 수 있다.

정적 파일을 배포해 CDN으로 캐싱할 수 있다.

안전하다.

웹 서버 외부에서 공격할 수 있는 것이 없다.

😟 SSG 단점

컨텐츠가 최신이 아니다.

빌드 시, 가져온 데이터를 계속 사용하기 때문에 상호작용하면서 작동하고 항상 최신의 데이터가 필요한 페이지에서는 알맞지 않다.

그러나 요즘에는 이런 단점이 개선된 방식이 등장했다. ISR로 이 단점을 해결할 수 있는데 이는 뒤에 알아보도록 하자.

사이트가 크다면 빌드 시 시간이 오래걸린다.

🔎 SSR (Server Side Rendering)

SSR은 페이지 요청이 들어왔을 때, 데이터를 가져오고 HTML을 사전 렌더링한다.
SSG와의 주요 차이점은 사전 렌더링을 진행하는 시점이다.
SSG는 빌드 시간에만 사전 렌더링하지만 SSR은 요청이 들어올 때 마다 사전 렌더링을 진행한다.

getServerSideProps 함수로 SSR 방법 사용하기

페이지 파일에 getStaticProps라는 함수를 사용해 페이지를 빌드 시간(build time)에 사전 렌더링(pre-render)할 수 있다.

export async function getServerSideProps(context) {
  return {
    props: {},
  }
}

🗺️ 함수를 사용할 수 있는 곳

오직 페이지 컴포넌트에서만 사용할 수 있으며 컴포넌트 내부가 아닌 컴포넌트 외부에 단독으로 사용해야한다.

⌚ 함수가 실행되는 시점

  1. URL이나 새로고침으로 페이지를 직접적으로 요청할 때
  2. next/link 또는 next/router를 사용하여 클라이언트 쪽에서 페이지를 전환할 때
    • 페이지를 요청하면 Next는 서버에 API요청을 전송하고 해당 요청은 getServerSideProps를 실행한다.

📄 직렬화 가능한 객체를 반환

export async function getServerSideProps(context) {
  return {
    props: { message: `Next.js is awesome` },
		...
  }
}

JSON.stringify로 직렬화될 수 있는 객체를 props로 반환하면 해당 객체는 페이지 컴포넌트의 props로 사용할 수 있다.

🗃️ SSR(Server-Side Rendering)을 사용하여 Caching

NextJs는 .next/static 폴더에 있는 정적파일에 자동으로 캐싱 헤더를 추가한다.

그렇다면 동적파일은 캐싱할 수 없을까?

getServerSideProps 함수 내부의 캐시 헤더(Cache-Control)을 사용하면 동적 응답을 캐시할 수 있다.

export async function getServerSideProps({ req, res }) {
  res.setHeader(
    'Cache-Control',
    'public, s-maxage=10, stale-while-revalidate=59'
  )

  return {
    props: {},
  }
}
  1. s-maxage=10 10초 동안 fresh 상태로 간주된다.
    • 만약 10초 이내에 요청이 반복된다면 이전 요청에서 캐시된 값은 여전히 fresh 상태이기 때문에 해당 값을 사용한다.
  2. stale-while-revalidate=59 59초 동안 stale 상태의 값이 유지된다.
    • 요청이 59초 전에 반복된다면 이전 요청에서 캐시된 값은 stale 상태이지만 여전히 렌더링된다.

특징

  1. getServerSideProps 내부에 사용하는 모듈을 최상위 최상위 scope에 import할 수 있다.
    • 서버에서만 실행되기 때문에 import는 클라이언트 측에 번들되지 않는다.
  2. getServerSideProps 내부에서 에러가 발생한다면 pages/500.js 파일이 표시된다.
    • NextJs는 별도의 파일 추가 없이 static 500 page를 제공한다.
    • 커스텀하게 하고 싶다면 pages/폴더에 500.js를 생성하여 작업할 수 있다.

😀 SSR 장점

데이터가 변경되도 다시 빌드하지 않아도 된다.

페이지 요청 마다 서버에서 데이터를 가져오고 페이지를 렌더링하기 때문에 항상 최신의 데이터를 유지한다.

검색 엔진 최적화에 친화적이다.

사전에 렌더링하기 때문에 페이지 소스에서 컨텐츠를 볼 수 있기 때문에 크롤러가 우리 페이지를 크롤링할 때 많은 컨텐츠를 제공할 수 있다.

컨텐츠가 항상 최신의 상태이다.

페이지 요청 마다 데이터를 가져오고 렌더링하기 때문에 최신의 데이터를 유지할 수 있다.

😟 SSR 단점

서버리스 배포 플랫폼에 배포할 수 없다.

Vercel, Netlify와 같은 서버리스 플랫폼에 배포할 수 없으며 AWS를 사용중이라면 S3에 빌드된 정적 파일을 올려 CDN으로 캐싱하는 등의 방법을 사용할 수 없다.

페이지의 첫 로드가 느리다.

또한 데이터를 가져올 때 까지 빈화면만 보여주기 때문에 사용자 입장에서는 페이지가 느리다고 생각할 수 있다.
하지만 React Suspense를 사용하여 fallback UI를 보여줄 수 있다.

🔎 CSR (Client Side Rendering)

위에서 알아본 SSG SSR과는 달리 CSR은 페이지가 렌더링된 후, 데이터를 가져온다. 일반적으로 useEffect()를 사용해 데이터를 가져온다.

useEffect(() => {
    setLoading(true)
    fetch('/api/profile-data')
      .then((res) => res.json())
      .then((data) => {
        setData(data)
        setLoading(false)
      })
  }, [])

😀 CSR 장점

컨텐츠가 항상 최신의 상태이다.

항상 최신의 데이터를 보여준다.

로딩 화면이 있다.

페이지에 필요한 모든 데이터를 가져오지 않았어도 로딩화면을 보여주는 등의 방법으로 사용자가 빈화면을 보고 있지 않아도 된다.

😟 CSR 단점

SEO에 친화적이지 않다.

HTML, JS 파일이 로드된 후 데이터를 가져오기 때문에 페이지 소스에 컨텐츠가 없다. 하지만 그렇다고 검색 엔진이 인덱싱할 수 없는 것은 아니다.
그래도 SSG SSR에 비하면 친화적이진 않다.

모든 컨텐츠를 로드하는 시간이 느리다.

페이지가 로드된 후 데이터를 요청하기 때문에 바로 모든 컨텐츠를 담은 화면을 볼수는 없다.

🔎 ISR (Incremental Static Regeneration)

위에서 SSG에 관한 내용을 다룰 때, getStaticProps를 사용할 때도 데이터를 최신으로 가져올 수 있다고 했었다.

ISR을 사용하면 페이지를 업데이트하기 위해 다시 빌드할 필요 없이 페이지 별로 SSG를 사용할 수 있다.

export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    revalidate: 10, // In seconds
  }
}

⌚ Next는 언제 페이지 재생성을 시도할까?

  1. 빌드 시, 사전 렌더링된 페이지에 대한 요청이 들어오면 처음에는 캐시된 페이지가 로드 된다.
  2. 초기 요청 이후, 10초(revalidate에 설정한 시간)가 지나기전 페이지 요청이 들어오면 이 경우 또한 즉각 캐시된 페이지가 로드된다.
    • 아직 컨텐츠가 stale한 상태가 아니기 때문이다.
  3. 10초가 지난 후, 컨텐츠가 state한 상태에서 요청이 들어오면 이 또한 캐시된(stale) 즉시, 페이지가 로드된다.
    • 이때 백그라운드에서 다음 요청시 제공될 새로운 정적 페이지를 생성한다.
    • 페이지가 성공적으로 생성되면 NextJs는 캐시를 무효화하고 업데이트된 페이지를 표시한다.
    • 페이지 생성이 실패한다면 캐시를 유지하고 이전 오래된 페이지가 제공된다.
  4. 페이지가 성공적으로 생성됐다면 이후 페이지 요청 시, 새롭게 캐시된 페이지가 로드된다.

😀 ISR 장점

빠르다.

정적 페이지를 로드하기 때문에 속도가 빠르다.

컨텐츠가 최신의 상태이다.

revalidate에 시간을 설정하면 설정된 시간이 지난 요청에 대해서는 최신의 컨텐츠를 제공한다.

SEO에 친화적이다.

사전에 렌더링하기 때문에 페이지 소스에서 컨텐츠를 볼 수 있기 때문에 크롤러가 우리 페이지를 크롤링할 때 많은 컨텐츠를 제공할 수 있다.

😟 SSG 단점

정적으로 생성이 늦어지면 해당 요청에 대한 페이지 제공은 늦어질 수 있다.

백그라운드에서 새로운 정적 페이지를 생성하는 것이 늦어지면 첫 로드가 느려질 수 있다.

결론

NextJs 제공하는 Data Fetching 방법 중 어떤것을 선택할 것인지는 개발하는 사람이 장단점을 파악하고 결정하는 것이 중요한 거 같다.
하지만 NextJs 측에서는 SSG를 추천한다.

참고한 글

https://dev.to/matfrana/server-side-rendering-vs-static-site-generation-17nf

https://dev.to/theodorusclarence/should-we-use-csr-ssr-ssg-or-isr-on-nextjs-1f29

https://theodorusclarence.com/blog/nextjs-fetch-method

0개의 댓글