컨셉

정적 사이트 렌더링(이하 SSG)은 서버 사이드 렌더링과 클라이언트 사이드 렌더링이 가진 대부분의 문제를 해결하지만 주로 정적인 컨텐츠를 렌더링하는 데 적합하다. 렌더링할 컨텐츠가 동적이거나 자주 변경되는 경우 제한되는 부분이 많다.

블로그가 점점글이 많아지며 성장한다고 가정해보자. 그런데 단지 게시물 중 하나의 오타를 수정하고 싶다는 이유만으로 사이트를 다시 구축하고 재배포하고 싶지는 않을 것입니다. 그리고 마찬가지로, 하나의 페이지 때문에 전체 페이지를 다시 빌드하는것은 비 효율적이다. 따라서 큰 사이트나 앱을 SSG로 렌더링 하는것은 애매한 부분이 있다.

점진적 정적 사이트 렌더링(이하 iSSG) 패턴은 SSG의 동적 데이터에 관련된 문제와 자주 변경되는 대량의 데이터에 대한 문제를 해결할 수 있는 패턴으로 도입되었다. iSSG를 사용하면 페이지에 대한 요청이 들어오는 중간에도 백그라운드에서 페이지들을 pre-rendering하여 기존 페이지를 업데이트하거나 새로운 페이지를 추가할 수 있다.

iSSG는 기존 정적 사이트가 구축된 후 점진적으로 업데이트를 도입하기 위해 두 가지 측면에서 작동한다.

  1. 새로운 페이지의 추가를 가능하게 한다
  2. 이미 존재하는 페이지를 재 생성한다

웹사이트가 빌드 된 후에 새로운 페이지를 추가하기 위해 지연 로딩의 컨셉이 사용된다. 이는 새로운 페이지가 첫 번째 요청이 들어온 즉시 만들어진다는 뜻이다.

페이지가 만들어지는 동안 폴백 페이지나 스피너가 화면에 보여진다. SSG만을 사용했을 때는 이와 달리 존재하지 않는 페이지에 대해 404에러 페이지가 보였었다.

export async function getStaticPaths() {
  const products = await getProductsFromDatabase()

  const paths = products.map(product => ({
    params: { id: product.id },
  }))

  // fallback: true means that the missing pages
  // will not 404, and instead can render a fallback.
  return { paths, fallback: true }
}

// params will contain the id for each generated page.
export async function getStaticProps({ params }) {
  return {
    props: {
      product: await getProductFromDatabase(params.id),
    },
  }
}

export default function Product({ product }) {
  const router = useRouter()

  if (router.isFallback) {
    return <div>Loading...</div>
  }

  // Render product
}

fallback: true 라는 값을 넘긴 것을 볼 수 있다. 이제 특정 상품에 관련된 페이지를 이용할 수 없을 경우 로딩 스피너와 같은 폴백 페이지가 노출된다. 그 동안에 Next.js는 백그라운드에서 해당 페이지를 만든다. 일단 해당 상세 페이지가 만들어지면, 그 페이지는 캐시되고, 폴백 페이지 대신에 보여진다. 이제 페이지의 캐시된 버전이 요청 즉시 이후 방문자들에게 즉시 노출된다(fallback없이). 새 페이지와 기존 페이지 모두에 대해. Next.js는 새로 만들거나 기존에 존재하는 페이지에 대하여 재 검증하고 업데이트를 하기 위해 캐시 만료 시간을 설정할 수 있다.

존재하는 페이지 업데이트하기

기존에 존재하는 페이지를 리렌더링하기 위해 페이지에 적합한 타임아웃을 정의할 수 있다.이는 정의한 타임아웃이 지나면 캐시를 만료하고 페이지를 재 생성(revalidate) 하도록 해 준다.제한 시간은 1초 정도까지 설정 가능하다. 사용자는 이 타임아웃이 끝날때까지 이전 버전의 페이지를 계속 보게 된다. 따라서 iSSG는 stale-while-revalidate 전략을 사용하여 재 생성이 이루어지는 와중에도 캐시된 버전이나 새로운 버전을 받아볼 수 있게 해 준다.이 재 생성 과정은 풀 리빌드 없이 백그라운드에서 진행된다.

데이터베이스의 데이터를 기반으로 제품에 대한 정적 목록 페이지를 생성하는 예제로 돌아가 보겠습니다.상품의 목록을 동적으로 처리하기 위해 코드에 타임아웃을 설정하여 재 빌드할 수 있도록 하였다. 아래는 타임아웃을 포함한 코드 예시이다.

// This function runs at build time on the build server
export async function getStaticProps() {
  return {
    props: {
      products: await getProductsFromDatabase(),
      revalidate: 60, // This will force the page to revalidate after 60 seconds
    },
  }
}

// The page component receives products prop from getStaticProps at build time
export default function Products({ products }) {
  return (
    <>
      <h1>Products</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </>
  )
}

getStateProps()에서 반환하는 객체의 타임아웃으로 인해 해당 페이지는 60초 마다 재 생성된다.

요청이 들어오면 기존의 정적 페이지가 먼저 서빙되고. 1분이 지날때 마다 해당 정적 페이지는 백그라운드에서 새로운 데이터를 사용하여 새로 만들어진다. 새로운 페이지가 만들어지면 이후 요청의 응답으로 제공된다.