Next.js의 프리 렌더링 옵션으로는 잘 알려진 SSG와 SSR 두 가지 외에도 ISR라고 불리는 한 가지 방식이 더 존재한다.

이 세 가지 프리 렌더링 방식의 특징과 차이점에 대해 알아보자.

 

참고로 하나의 애플리케이션에서 하나의 방식만 사용해야 하는 것은 아니며, 필요에 따라 어떤 페이지에선 SSG 방식을 사용하고 나머지 페이지에선 SSR 방식을 사용하는 것도 가능하다.

또한 이 방식들과 클라이언트 사이드 렌더링 방식을 혼합하여 사용하는 것도 가능하다. (즉 어떤 페이지의 특정 부분은 전적으로 클라이언트 사이드에서 렌더링 되도록 하는 방식)

 

Pre-rendering이란?
Next.js에선 기본적으로 모든 페이지를 프리 렌더링한다. 즉 HTML이 클라이언트 사이드에서 자바스크립트를 통해 생성이 되는 게 아니라 Next.js가 사전에 각 페이지를 만들어 놓는다. 이때 각 HTML은 해당 페이지에 최소한으로 필요한 자바스크립트 코드와 결합되어있다. 페이지가 브라우저에 의해 로드될 때는 hydration이라는 프로세스를 거치게 된다. hydration은 자바스크립트 코드가 실행되어 해당 페이지를 완전히 인터렉티브하게 만드는 것을 의미한다.
프리 렌더링을 할 경우, 프리 렌더링을 하지 않을 때보다 더 나은 성능과 SEO를 갖출 수 있다.

*Next.js는 프리 렌더링 및 서버사이드 렌더링을 포함한 페이지 렌더링을 위한 런타임으로 Node.js를 사용한다.

 

- 아래 내용에서 외부 API로 요청을 보낸다고 표현한 경우를 제외한 '요청'의 의미는, 어떤 '페이지를 요청한다'는 의미로 사용했다.

- /about path 진입 = '어바웃 페이지를 요청한다.'

 

Static-site Generation (SSG)

  • HTML이 빌드 타임에 생성되며, 매 요청 시 재사용 된다.
  • SSR 방식보다 성능이 더 좋고 빠르다고 한다.
    • 정적으로 생성된 페이지는 한 번 생성되면 CDN에 캐시될 수 있기 때문에, SSR 방식을 통해 매 요청마다 페이지를 생성하는 경우보다 훨씬 빠르다고 한다.
  • 외부 데이터가 없는 경우와 있는 경우 모두 정적 페이지 생성이 가능하다.
    • 데이터가 없는 경우 (예: 회사 소개 페이지) 
      • Next.js는 외부 데이터를 가져올 필요가 없는(=data fetch를 하지 않는) 페이지는 SSG 방식으로 프리 렌더링한다.
    • 데이터가 있는 경우 - 두 가지 시나리오가 있는데, 하나만 적용할 수도 있고 둘 다 적용할 수도 있다.
      • 시나리오1: 페이지의 내용이 외부 데이터에 의존한다면 getStaticProps를 쓸 것 (예: CMS로부터 블로그 포스트 목록을 가져오려고 할 때)
      • 시나리오2: 페이지의 경로가 외부 데이터에 의존한다면 getStaticPaths를 쓸 것 (예: 동적 라우트(dynamic routes)로 pages/posts/[id].js 페이지를 생성하려는 경우 id 값에 들어갈 값들을 먼저 받아와야 할 때)
/* 시나리오1 */

function Blog({ posts }) {
  // 포스트 목록 렌더링...
}

// getStaticProps는 빌드 시에 호출된다.
export async function getStaticProps() {
  // 블로그 포스트들을 가져오기 위해 외부 API 엔드포인트를 통해 요청을 보낸다.
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // { props: { posts } } 객체를 반환하면 블로그 컴포넌트가 빌드 타임에
  // `posts`를 prop으로 전달 받게 된다.
  return {
    props: {
      posts,
    },
  }
}

export default Blog
/* 시나리오2 */

function Post({ post }) {
  // 개별 포스트 렌더링..
}

// getStaticPaths는 빌드 시에 호출된다.
export async function getStaticPaths() {
  // 블로그 포스트들을 가져오기 위해 외부 API 엔드포인트를 통해 요청을 보낸다.
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // 프리 렌더링 해야 하는 페이지 경로를 `posts`로부터 뽑아낸다. 
  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // 이렇게 { paths, fallback: false } 객체를 반환하면,
  // `paths`에 포함된 페이지들만 빌드 시에 프리 렌더링 된다.
  // { fallback: false }에 따라 다른 라우트는 404가 된다.
  return { paths, fallback: false }
}

// getStaticProps도 역시 빌드 시에 호출된다.
export async function getStaticProps({ params }) {
  // params에는 포스트의 `id`가 포함되어 있다.
  // (예를 들어 라우트가 /posts/1일 경우, params.id는 1)
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

  // 포스트 데이터를 페이지에 props로 전달한다.
  return { props: { post } }
}

export default Post
  • SSG 방식은 언제 사용하는 것이 좋은가?
    • 마케팅 페이지
    • 블로그 포스트
    • 이커머스 상품 목록
    • 등등.. 사용자의 요청에 앞서 프리 렌더링해도 되는 페이지
  • 다음의 경우에는 SSG 방식이 적합하지 않다.
    • 사용자의 요청에 앞서 프리 렌더링할 수 없는 페이지
    • 예를 들어, 페이지가 빈번하게 변경 된다거나, 페이지의 내용이 매 요청마다 변경되어야 하는 페이지
  • SSG 방식이 적합하지 않은 경우의 대안
    • SSG와 클라이언트 사이드 렌더링 방식 함께 사용하기: 페이지의 특정 부분에선 프리 렌더링을 하지 않고, 그런 부분은 클라이언트 사이드에서 자바스크립트를 통해 생성되도록 한다.
    • SSR 사용하기: SSR 방식을 사용하여 생성하는 페이지는 CDN에 캐시될 수는 없어 속도는 느려질 수 있지만, 매 요청마다 다시 프리 렌더링되기 때문에 늘 최신화(up-to-date) 된 데이터를 보여줄 수 있다. 

 

Server-side Rendering (SSR)

  • HTML이 매 요청마다 생성된다.
  • getServerSideProps를 사용하여 서버사이드 렌더링을 할 수 있다.
  • getServerSideProps vs getStaticProps
    • getServerSideProps 👉 매 요청 시에 실행된다.
    • getStaticProps 👉 빌드 시에 실행된다. (즉 딱 한 번 실행된다.)
function Page({ data }) {
  // data를 렌더링..
}

// getServerSideProps는 매 요청 시 호출된다.
export async function getServerSideProps() {
  // 외부 API로부터 데이터를 가져온다.
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // 이렇게 가져온 데이터를 페이지에 props로 전달한다.
  return { props: { data } }
}

export default Page
  • 공식 문서에선 SSR이 SSG에 비해 느리기 때문에 절대적으로 필요한 경우에만 사용할 것을 권고하고 있다.

 

Incremental Static Regeneration (ISR)

  • stale-while-revalidate 캐싱 전략을 따르는 일종의 하이브리드 방식이다.
  • ISR을 사용하면 SSR을 사용할 때보다 성능을 향상시킬 수 있고 사용성도 개선할 수 있다고 한다.
  • ISR은 페이지 단위로 정적 생성되도록 하는 방식이다. 즉, 전체 사이트를 다시 빌드하지 않아도 된다는 것이다.
  • 어떻게 사용하는가?
    • getStaticPropsrevalidate prop을 추가해주면 된다.
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

// getStaticProps는 서버사이드에서 빌드 시에 호출된다.
// 유효성을 재확인(revalidation)하도록 설정해두었다면, 새로운 요청이 들어올 때 다시 호출된다.
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    // Next.js는 요청 후 10초가 지난 이후에 또 요청이 들어오면 페이지를 다시 생성하려는 시도를 한다.
    revalidate: 10, // 단위: 초
  }
}

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

  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  return { paths, fallback: 'blocking' }
}

export default Blog
  • 어떻게 동작하는가? (위 예제 코드 기반으로 설명)
    • 빌드 타임에 프리 렌더된 페이지에 대해 요청이 발생하면, 우선은 초기에는 캐시된 페이지를 보여준다.
    • 이 페이지에 대한 최초 요청 이후 10초 내로 들어오는 모든 요청들은 캐시된다.
    • 10초 이후 들어오는 요청에도 일단은 캐시된(즉, stale한) 페이지가 표시된다.
      • 요청이 들어온 순간 재생성을 시작하기 때문에 그 전까지 캐시된 페이지를 보여주는 것이다.
    • Next.js는 백그라운드에서 페이지를 재생성하기 시작한다.
      • 페이지 재생성이 성공하면, Next.js는 캐시된 것은 더 이상 유효하지 않도록 만들고, 업데이트된 페이지를 보여준다.
      • 페이지 재생성이 실패하면, 기존 페이지가 변경되지 않고 계속 보여진다.
    • 만약 10초(즉 revalidate 기준 시간)가 흐른 뒤에도 해당 페이지에 대한 새로운 요청이 발생하지 않는다면 페이지 재생성은 이루어지지 않는다.

 


참고

 

Basic Features: Pages | Next.js

Next.js pages are React Components exported in a file in the pages directory. Learn how they work here.

nextjs.org

 

The two and a half + one flavors of next.js's pre-rendering

Confused by the title? Don't be, we will take a look at the different pre-rendering options provided...

dev.to

 

Data Fetching: Overview | Next.js

Next.js allows you to fetch data in multiple ways, with pre-rendering, server-side rendering or static-site generation, and incremental static regeneration. Learn how to manage your application data in Next.js.

nextjs.org

 

[Next.js] Incremental Static Regeneration의 revalidate (re-generation)테스트

Next.js의 ISR(Incremental Static Regeneration)을 위해 'revalidate: Number' 값을 주었을때의 동작순서 확인하기 (아래 페이지의 Fetching Data 부분을 정확히 이해하기)https://vercel.com/

velog.io

 

복사했습니다!