Next.js를 사용하면 정적 페이지를 만들거나 업데이트 할 수 있다.
이때 Incremental Static Regeneration(ISR)을 사용하면 전체 사이트를 다시 빌드하지 않고도 페이지당 static generation을 할 수 있게 된다.
Incremental Static Regeneration을 사용하면 static이라는 이점을 유지하면서 수백만 페이지로 확장할 수 있다.
static generation 방식이 항상 기존의 데이터를 유지하는 것이 아니라,
ISR을 통해 데이터를 최신으로 유지할 수 있다는 점을 기억하자.
ISR을 사용은 매우 간단하다.
getStaticProps에 revalidate prop을 추가하면 된다.
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
// 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, // In seconds
}
}
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
// getStaticPaths 함수는 서버측에서 빌드될 때 호출된다.
// 경로가 생성되지 않았을 때 getStaticPaths 함수는 다시 호출될 것이다.
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
// pre-rendering할 posts의 경로를 가져온다.
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// We'll pre-render only these paths at build time.
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
// 빌드시 생성한 경로만 pre-rendering이 진행된다.
// { fallback: blocking }으로 설정한다면
// 경로가 존재하지 않는 경우 on-demand 방식으로 페이지를 처리한다.
return { paths, fallback: 'blocking' }
}
export default Blog
빌드 때 pre-render된 페이지를 요청하면 최초에는 caching된 페이지를 보여준다.
초기 요청 이후 10초가 지나기 전까지는 페이지에 대한 모든 요청도 즉각적으로 cache되며 10초가 지났을때의 요청은 stale cahed된 페이지를 보여준다.
Next.js는 백그라운드에서 페이지 재생성을 트리거한다.
페이지 생성이 성공한다면 기존 캐시를 무효화하고 업데이트 된 페이지를 보여준다. 만약 백그라운드에서 페이지 재생성이 실패한다면, 기존의 페이지는 여전히 변경되지 않은 상태를 보여줄 것이다.
생성되지 않은 경로에 대해 요청이 발생할 때, Next.js는 첫번째 요청에 대해
server-render를 진행할 것이다.
이후 요청은 cache로부터 정적파일을 제공 받는다.
Vercel을 사용한다면 ISR은 캐시를 전역적으로 유지하고 롤백 처리한다.
revailidate을 60초로 설정했다면, 모든 사이트 방문자들은 1분 동안 같은 버전의 페이지를 볼것이다.
캐시를 무효화하는 유일한 방법은 누군가 해당 페이지를 방문하는 것이다.
Next.js는 v12.1.0부터 On-Demand(주문형)ISR을 지원하여 특정 페이지의 Next.js 캐시를 수동으로 제거할 수 있게 한다.
이렇게 되면 Headless CMS의 콘텐츠가 생성 또는 업데이트되며, Ecommerce사이트의 메타데이터(가격,정보,카테고리,리뷰)가 변경될 때 쉽게 업데이트 할 수 있게된다.
getStaticProps 내에서, On-demand 방식의 재검증을 사용하기 위해 또 다른 재검증을 지정할 필요가 없다.
revalidate 속성이 생략되면 Next.js는 기본값인 false(no revalidation)를 사용하고, revalidate가 호출 될 때에만 재검증을 진행한다.
On-Demand Revalidation을 사용하기 위해서 먼저 Next.js 앱에서만 사용할 비밀 토큰을 만들어야 한다.
이 비밀 토큰은 무단 엑세스를 방지하는데 사용된다.
https://<your-site.com>/api/revalidate?secret=<token>
그런 다음 비밀토큰을 어플리케이션의 환경 변수로 추가하고 revalidation API
Route를 생성한다.
// pages/api/revalidate.js
export default async function handler(req, res) {
// Check for secret to confirm this is a valid request
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' })
}
try {
await res.unstable_revalidate('/path-to-revalidate')
return res.json({ revalidated: true })
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send('Error revalidating')
}
}
getStaticProps 내에서 백그라운드생성을 다룰 때 오류가 있거나 수동적으로 오류를 발생시키면, 가장 최근에 성공적으로 생성된 페이지가 계속해서 보일것이다. 다음 후속 요청에서 Next.js는 getStaticProps 호출을 다시 시도한다.
export async function getStaticProps() {
// If this request throws an uncaught error, Next.js will
// not invalidate the currently shown page and
// retry getStaticProps on the next request.
// 사용자의 요청이 에러처리가 되지 않은 에러라면,
// Next.js는 현재 보여주는 페이지를 무효화하지 않고
// getStaticProps의 다음 요청을 재시도할 것이다.
const res = await fetch('https://.../posts')
const posts = await res.json()
if (!res.ok) {
// If there is a server error, you might want to
// throw an error instead of returning so that the cache is not updated
// until the next successful request.
// 서버에 오류가 있다면 기존 페이지를 업데이트 되지 않게 하는 것보다
// 다음 요청이 성공할 때까지 오류를 보여줄 수도 있다.
throw new Error(`Failed to fetch posts, received status ${res.status}`)
}
// If the request was successful, return the posts
// and revalidate every 10 seconds.
// 요청이 성공적이라면, 포스트로 돌아가고, 10초마다 재검증한다.
return {
props: {
posts,
},
revalidate: 10,
}
}