Next.js의 SSR, SSG, Dynamic Routing 알아보기
Prog. Langs & Tools/Next.js

Next.js의 SSR, SSG, Dynamic Routing 알아보기

Next.js 프레임워크는 Data Fetching 시에 SSR(Server-side Rendering), SSG(Static-site Generation), 그리고 Dynamic Routing 등의 세 가지 방법을 제공한다. 각각의 방법에 대해서 하나씩 깊게 알아보고 언제 어떻게 써야 하는지 사용법을 공부해 보고자 한다.

 

SSR(Server-side Rendering)

만약 어떤 페이지에서 getServerSideProps 함수를 호출하면, Next.js는 getServerSideProps에서 데이터를 반환받을 때 마다 이 페이지를 pre-rendering 할 것 이다.

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

여기서 주의할 점. 렌더링 타입에 상관없이, 어떠한 props 든지 페이지 컴포넌트에 전달이 될 것이고 클라이언트 사이드 초기 HTML에 보이게 될 것이다. 이는 페이지가 정확하게 hydrated 되도록 허용한다. 명심해야 하는 점은, props에 민감한 정보를 절대 보내면 안된다.

그렇다면 언제 getServerSideProps 가 실행되는가?

getServerSideProps 는 오직 서버 사이드에서만 실행되고 절대 브라우저에서는 실행되지 않는다. 만약 페이지가 getServerSideProps를 사용한다면,

  • 이 페이지를 직접 요청하는 경우, getServerSideProps는 요청 시간에 실행이 되고 이 페이지는 pre-rendered 되어 props를 반환할 것이다.
  • 이 페이지를 클라이언트 사이드의 페이지 이동으로 요청하는 경우, Next.js는 API 요청을 서버에 보내고, 이는 getServerSideProps를 실행시킨다.

getServerSideProps는 JSON을 반환하고 이 값은 페이지를 렌더링하는데 사용된다.

언제 getServerSideProps를 사용해야 하는가? 오직 데이터가 요청 시간에만 패치가 되는 페이지를 렌더링 할 때 사용해야 한다. 예를 들면 authorization 헤더나 geolocation 값 같은 것들이 될 수 있다. getServerSideProps를 사용하는 페이지들은 요청한 시간에 서버 사이드 렌더링이 될 것이고 cache-control 헤더가 설정 되었을 때만 캐시될 것이다.

SSR에서 클라이언트 사이드 데이터 패칭 방법은 없을까? 만약 페이지가 빈번하게 업데이트 되는 데이터를 가지고 있고, 데이터를 pre-render 할 필요가 없을 때, 클라이언트 사이드 데이터 패치를 할 수 있다.

  • 먼저, 페이지를 데이터 없이 즉시 보여준다. 페이지의 일 부분은 정적 생성(Static Generation)을 통해 pre-render 될 것이다. 빠진 데이터는 로딩 상태를 보여준다.
  • 그리고 나서 클라이언트 사이드에서 데이터를 패치하고 준비되면 보여준다.

이러한 접근은 예를 들면 사용자 대시보드 같은 페이지에 잘 동작한다. 왜냐하면 대시보드는 프라이빗하고, 사용자 특정된 페이지이며, SEO가 필요 없고 페이지가 pre-render 될 필요가 없기 때문이다. 데이터가 빈번하게 업데이트 되기 때문에 이는 요청 시간 데이터 패칭을 요구한다.

아래 코드는 요청 시간에 데이터 패치를 어떻게 할 수 있는지 그리고 결과를 어떻게 pre-render 할 수 있는지 코드로 작성한 예시이다.

function Page({ data }) {
  // Render data...
}

// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // Pass data to the page via props
  return { props: { data } }
}

export default Page

getServerSideProps 안에 Cache-Control 이라는 캐싱 헤더를 넣어서 동적인 응답을 캐싱할 수 있다. 예시는 다음과 같다.

// This value is considered fresh for ten seconds (s-maxage=10).
// If a request is repeated within the next 10 seconds, the previously
// cached value will still be fresh. If the request is repeated before 59 seconds,
// the cached value will be stale but still render (stale-while-revalidate=59).
//
// In the background, a revalidation request will be made to populate the cache
// with a fresh value. If you refresh the page, you will see the new value.
export async function getServerSideProps({ req, res }) {
  res.setHeader(
    'Cache-Control',
    'public, s-maxage=10, stale-while-revalidate=59'
  )

  return {
    props: {},
  }
}

 

Dynamic Routing

어떤 동적 라우팅(Dynamic Route)을 사용하는 페이지에서 getStaticPaths를 호출하면 Next.js는 getStaticPaths로 특정된 모든 경로를 전부 정적으로 pre-render 한다.

// pages/posts/[id].js

// Generates `/posts/1` and `/posts/2`
export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    fallback: false, // can also be true or 'blocking'
  }
}

// `getStaticPaths` requires using `getStaticProps`
export async function getStaticProps(context) {
  return {
    // Passed to the page component as props
    props: { post: {} },
  }
}

export default function Post({ post }) {
  // Render post...
}

getStaticPaths는 언제 사용해야 할까? getStaticPaths는 동적 라우팅을 사용하며 정적으로 pre-render가 일어나야 하는 페이지에서 사용되어야 하며 다음과 같은 조건에서 사용한다.

  1. 데이터가 headless CMS에서 오는 경우
  2. 데이터가 데이터베이스에서 오는 경우
  3. 데이터가 파일 시스템에서 오는 경우
  4. 데이터가 공개적으로 캐시 되는 경우
  5. 페이지가 SEO를 위해서 pre-render 되어야 하고 빠르게 나타나져야 하는 경우.
    1. getStaticProps 는 HTML 과 JSON 파일을 만드는데 둘 다 성능을 위해서 CDN에 의해 캐싱될 수 있다.

getStaticPaths는 프로덕션 빌드 동안에만 실행이 되고, 런타임에는 실행되지 않는다.

getStaticProps 가 getStaticPaths 와 관련해서 실행이 되는 경우는 다음과 같다.

  • getStaticProps 는 next build 동안 빌드 중에 반환된 경로들에서 실행된다.
  • getStaticProps 는 fallback : true 일 경우 백그라운드에서 실행된다.
  • getStaticProps 는 fallback : blocking 일 경우 최초 렌더링 전에 호출된다.

getStaticPaths는 어떤 페이지가 빌드 중에 생성이 될지를 조절할 수 있게 해준다. 빌드 해야 할 페이지가 많으면 빌드가 느려진다. paths에 빈 배열을 넣으면 모든 페이지를 생성하는 작업을 필요에 따라 연기할 수 있다. 이는 Next.js로 여러가지 환경에서 어플리케이션을 배포해야 할 때 도움이 될 수 있다. 예를 들면, 프리뷰에 필요에 맞춰서 모든 페이지 생성을 조절해서 빌드를 빠르게 할 수 있다. 이는 정적인 페이지가 수백, 수천개 이상인 사이트에서 유용하다.

// pages/posts/[id].js

export async function getStaticPaths() {
  // When this is true (in preview environments) don't
  // prerender any static pages
  // (faster builds, but slower initial page load)
  if (process.env.SKIP_BUILD_STATIC_GENERATION) {
    return {
      paths: [],
      fallback: 'blocking',
    }
  }

  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // Get the paths we want to prerender based on posts
  // In production environments, prerender all pages
  // (slower builds, but faster initial page load)
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // { fallback: false } means other routes should 404
  return { paths, fallback: false }
}

 

SSG(Static-site Generator)

페이지에서 getStaticProps 를 호출하면, Next.js는 getStaticProps에서 반환한 props를 사용하여 빌드 시간에 페이지를 pre-render 한다.

export async function getStaticProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

getStaticProps는 언제 사용해야 할까? 다음과 같은 경우에 사용할 수 있을 것이다.

  • 페이지를 렌더링하는데 필요한 데이터가 사용자의 요청 전에 빌드 시간에 가능할 때
  • 데이터가 headless CMS에서 불러와질 때
  • 페이지가 SEO를 위해 pre-render 되어야 하고 빠르게 실행되어야 할 때
  • 데이터가 공개적으로 캐싱 될 수 있을 때

getStaticProps는 그렇다면 언제 실행이 될까? getStaticProps는 항상 서버에서 실행이 되고 클라이언트에서는 실행되지 않는다. 항상 next build 동안에 실행이 된다.

  • 만약 fallback: true 이면 백그라운드에서 실행이 된다.
  • 만약 fallback: blocking이면 최초 렌더링 이전에 호출이 된다.
  • revalidate 를 사용하면 백그라운드에서 실행이 된다.
  • revalidate() 를 사용하면 요청에 맞게 백그라운드에서 실행이 된다.

Incremental Static Regeneration과 결합하여, getStaticProps는 오래된 페이지가 갱신될 때 백그라운드에서 실행된다. 그리고 나서 새로운 페이지가 브라우저에서 보여진다.

getStaticProps 는 정적인 HTML을 만들 때 쿼리 파라미터나 HTTP 헤더처럼 인입되는 요청에 접근할 수 없다.

아래의 예제는 CMS에서 블로그 포스트 리스트를 어떻게 패치하는지 보여준다.

// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  )
}

// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export async function getStaticProps() {
  // Call an external API endpoint to get posts.
  // You can use any data fetching library
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // By returning { props: { posts } }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts,
    },
  }
}

export default Blog

getStaticProps는 서버 사이드에서 실행이 되므로, 클라이언트 사이드에서는 절대 실행되지 않는다. 브라우저의 자바스크립트 번들에도 포함되지 않으며, 따라서 직접적인 데이터베이스 쿼리를 브라우저에 보내지 않고 작성할 수 있다.

이는 getStaticProps에서 API Route를 통해 패칭하는 대신, 서버 사이드 코드를 바로 getStaticProps에 작성할 수 있다는 의미이다. 아래의 예제는 데이터를 CMS에서 패칭하는 로직이 lib/ 디렉토리에 공유가 되고 이 로직이 getStaticProps와 공유될 수 있음을 나타내는 예제 코드이다.

// lib/load-posts.js

// The following function is shared
// with getStaticProps and API routes
// from a `lib/` directory
export async function loadPosts() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts/')
  const data = await res.json()

  return data
}

// pages/blog.js
import { loadPosts } from '../lib/load-posts'

// This function runs only on the server side
export async function getStaticProps() {
  // Instead of fetching your `/api` route you can call the same
  // function directly in `getStaticProps`
  const posts = await loadPosts()

  // Props returned will be passed to the page component
  return { props: { posts } }
}

대안적으로, 데이터를 패치할 때 API Route를 사용하지 않으면 fetch() API가 getStaticProps 안에서 직접 데이터를 패치할 때 사용될 수 있다.

getStaticProps 로 빌드 시간에 페이지가 pre-render 될 때, Next.js는 HTML 파일 뿐만 아니라 JSON 파일을 만들어서 실행중인 getStaticProps에서 반환시킨다. 이 JSON 파일이 클라이언트 사이드 라우팅에서 사용될 수 있다. getStaticProps로 pre-render된 페이지를 탐색할 때, Next.js는 이 JSON 파일을 패치하여 페이지 컴포넌트에 props로 보낸다. 이는 클라이언트 사이드 트랜지션은 getStaticProps를 부르지 않으며 오직 추출된 JSON만 사용한다는 의미이다.

 

참고자료

https://nextjs.org/docs/basic-features/data-fetching/overview

 

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