Incremental Static Generation
Incremental Static Generation
정적 사이트 렌더링(이하 SSG)은 서버 사이드 렌더링과 클라이언트 사이드 렌더링이 가진 대부분의 문제를 해결하지만 주로 정적인 컨텐츠를 렌더링하는 데 적합하다. 렌더링할 컨텐츠가 동적이거나 자주 변경되는 경우 제한되는 부분이 많다.
여러 포스트들이 작성되며 규모가 커 지는 블로그를 생각해 보자. 게시물의 오타를 하나 수정했다고 사이트 전체를 재 배포하고싶지는 않을 것이다. 하나의 페이지 때문에 전체 페이지를 다시 빌드하는것은 비 효율적이다. 따라서 큰 사이트나 앱을 SSG로 렌더링 하는것은 애매한 부분이 있다.
점진적 정적 사이트 렌더링(이하 iSSG) 패턴은 SSG의 동적 데이터에 관련된 문제와 자주 변경되는 대량의 데이터에 대한 문제를 해결할 수 있는 패턴으로 도입되었다. iSSG를 사용하면 페이지에 대한 요청이 들어오는 중간에도 백그라운드에서 페이지들을 pre-rendering하여 기존 페이지를 업데이트하거나 새로운 페이지를 추가할 수 있다.
iSSG - 예제 코드
iSSG는 기존에 빌드되었던 정적 사이트를 업데이트하기 위해 두 가지 측면에서 동작한다.
- 새로운 페이지의 추가를 가능하게 한다
- 이미 존재하는 페이지를 재 생성한다
새로운 페이지 추가하기
웹사이트가 빌드 된 후에 새로운 페이지를 추가하기 위해 지연 로딩의 컨셉이 사용된다. 이는 새로운 페이지가 첫 번째 요청이 들어온 즉시 만들어진다는 뜻이다. 페이지가 만들어지는 동안 폴백 페이지나 스피너가 화면에 보여진다. SSG만을 사용했을 때는 이와 달리 존재하지 않는 페이지에 대해 404에러 페이지가 보였었다.
아래는 존재하지 않는 페이지에 대해 iSSG를 적용하여 지연로딩이 되는 Node.js 코드이다.
// In getStaticPaths(), you need to return the list of
// ids of product pages (/products/[id]) that you’d
// like to pre-render at build time. To do so,
// you can fetch all products from a database.
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
}
이 예제에서는 getStateProps()
에서 반환되는 객체에 fallback: true
가 전달되고 있다. 이제 특정 상품에 관련된 페이지를 이용할 수 없을 경우 로딩 스피너와 같은 폴백 페이지가 노출된다. 그 동안 Next.js는 백그라운드에서 해당 페이지를 만든다. 페이지가 만들어지고 나면 캐시되어 폴백 페이지 대신 보여지게 된다. 이렇게 캐시된 페이지는 이후 방문자들에게 즉시 노출된다. Next.js는 새로 만들거나 기존에 존재하는 페이지에 대하여 재 검증하고 업데이트를 하기 위해 캐시 만료 시간을 설정할 수 있다. 이는 뒤이어 다룰 revalidate
프로퍼티에 의해 설정 가능하다.
존재하는 페이지를 업데이트하기
기존에 존재하는 페이지를 리렌더링하기 위해 페이지에 적합한 타임아웃을 정의할 수 있다. 이는 정의한 타임아웃이 지나면 캐시를 만료하고 페이지를 재 생성(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분이 지날때 마다 해당 정적 페이지는 백그라운드에서 새로운 데이터를 사용하여 새로 만들어진다. 새로운 페이지가 만들어지면 이후 요청의 응답으로 제공된다. 이 기능은 Next.js 9.5 이상에서 사용 가능하다.
iSSG 의 장점
iSSG는 SSG의 장점뿐만 아니라 아래 정리된 추가적인 이점을 제공한다.
- 동적 데이터: 이는 iSSG가 탄생한 근본적인 이유인데. 전체 사이트를 다시 빌드하지 않아도 동적 데이터를 지원할 수 있다.
- 속도: iSSG는 데이터를 받는 것과 렌더링을 백그라운드에서 진행하기 때문에 최소 SSG보다는 빠르다. 클라이언트와 서버에서 처리해야 하는 작업은 매우 작다.
- 가용성: 그나마 최신 버전의 페이지들은 계속 서빙이 가능하다. 재 생성 과정이 실패하더라도 예전 버전은 언제든 서빙이 가능하다.
- 지속성: 페이지 재 생성 과정은 서버에서 한번에 하나씩 이뤄지기 때문에 데이터베이스와 백엔드 서버의 부하가 적고 일정한 성능을 유지할 수 있다. 그리하여 레이턴시가 불안정하게 튀는 현상이 없다.
- 배포가 쉽다: SSG사이트 처럼 iSSG사이트 역시 pre-render된 페이지 서빙을 위해 CDN을 사용할 수 있다.