View
next.js 12.2버전부터 안정화된 기능입니다.
Pgaes Router 기반으로 작성하였습니다.
On-Demand Revalidation 방식은 원하는 시점에 정적 페이지를 업데이트할 수 있는 기능입니다. 전체 사이트를 새롭게 배포하지 않고도 특정 페이지를 업데이트할 수 있다는 점에서 기존의 ISR 방식과 유사하지만, 특정 시간마다 재검증이 이루어지는 것이 아니라 필요한 시점에 재검증을 수행할 수 있다는 점에서 차이가 있습니다.
Incremental Static Regeneration (ISR)
먼저, ISR을 사용하기 위해서는 getStaticProps 함수의 리턴값으로 revalidate 속성을 추가해야 합니다.
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every 5 seconds
revalidate: 5, // In seconds
}
}
다음과 같이 revalidate을 5초로 설정한다면 아래와 같이 동작합니다.
응답 헤더의 x-nextjs-cache 값으로 페이지의 캐싱 상태를 확인할 수 있습니다.
브라우저가 처음 페이지를 요청하면 fresh 상태의 페이지를 응답받습니다. revalidate 설정 시간인 5초 뒤 데이터가 갱신되었다면 서버는 프리 렌더링을 시작하며, 페이지 생성이 완료되기 전까지는 이전의 stale한 페이지를 브라우저로 보내줍니다. 프리 렌더링 작업이 완료된 후 브라우저를 새로고침하면 새롭게 생성된 HIT 상태의 페이지를 볼 수 있게 됩니다.
만약 revalidate를 60으로 설정한다면 모든 방문자는 60초 동안 동일한 페이지를 보게 되고, 캐시를 무효화하고 페이지를 업데이트하기 위해서는 누군가가 60초가 지난 시점에 해당 페이지를 방문해야만 합니다. 이러한 방식은 페이지가 언제 다시 생성되고 어떤 데이터가 프리 렌더링 되었는지 추적하기 어렵고, 페이지에서 캐시가 너무 오래 지속될 경우에 페이지의 업데이트가 사용자에게 즉각적으로 반영되지 않을 수 있다는 문제점이 있었습니다. 이를 해결하기 위해 next.js 12.2에서는 캐시를 원하는 시점에 수동으로 제거할 수 있는 On-Demand Revalidation 방식을 지원합니다.
On-Demand Revalidation
On-Demand Revalidation 방식으로 실행하려면 revalidate 속성을 제거한 후, API Route를 생성하여 res.revalidate() 메서드를 호출하는 로직을 추가해 줍니다.
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 {
// this should be the actual path not a rewritten path
// e.g. for "/blog/[slug]" this should be "/blog/post-1"
await res.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')
}
}
On-Demand Revalidation 방식을 사용하면 페이지의 캐시를 원하는 시점에 무효화하고 업데이트할 수 있습니다. 이를 적절히 사용한다면 페이지의 캐싱 문제를 해결하고, 불필요한 페이지 요청을 줄여 Build 시간과 리소스 사용을 줄일 수 있을 것 같습니다.
코드 예제 source code
development 환경에서 실행할 때는 모든 요청마다 getStaticProps가 호출되기 때문에, production으로 실행해야 합니다.
# page/post/[id].tsx
import { useRouter } from 'next/router';
import type {
InferGetStaticPropsType,
GetStaticProps,
GetStaticPaths,
} from 'next';
interface Post {
id: number;
title: string;
description: string;
timestamp?: number;
}
export default function Page({
data: { id: listId, title, description, timestamp = 0 },
}: InferGetStaticPropsType<typeof getStaticProps>) {
const router = useRouter();
const { id } = router.query;
const revalidate = async () => {
await fetch(`http://localhost:3000/api/revalidate?secret=1234`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ id }),
});
};
return (
<div>
<p>post: {listId} {title} {description}</p>
<p>timestamp: {timestamp}</p>
<button
className="p-2 rounded-md text-black bg-green-200 ease-linear hover:bg-green-300"
onClick={revalidate}
>
revalidate
</button>
</div>
);
}
export const getStaticPaths = (async () => {
const res = await fetch('http://localhost:4000/list');
const data: Post[] = await res.json();
const paths = data.map(({ id }) => ({
params: { id: String(id) },
}));
return { paths, fallback: 'blocking' };
}) satisfies GetStaticPaths;
export const getStaticProps = (async (context) => {
try {
const { id } = context.params!;
const res = await fetch(`http://localhost:4000/list/${id}`);
const data: Post = await res.json();
return {
props: { data: { ...data, timestamp: Date.now() } },
// on-demand revalidation 적용 시 revalidate 속성 제거
// revalidate: 5,
};
} catch (err) {
return {
notFound: true,
};
}
}) satisfies GetStaticProps<{
data: Post;
}>;
# page/api/revalidate.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { body } = req;
if (req.query.secret !== process.env.SECRET_REVALIDATE_TOKEN) {
return res.status(401).json({ message: 'Invalid token' });
}
try {
await res.revalidate(`/post/${body.id}`);
return res.json({ revalidated: true });
} catch (err) {
return res.status(500).send('Error revalidating');
}
}
참고
[docs]
Incremental Static Regeneration
[blog]
'Frontend > Next.js' 카테고리의 다른 글
Next.js 13으로 버전 업데이트하기(Pages Router) (0) | 2023.05.09 |
---|---|
Next.js 개념 이해하기 (0) | 2022.09.14 |
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Total
- Today
- Yesterday
- https
- javascript
- Network
- async-await
- HTTP
- 운영체제
- IntersectionObserverAPI
- Promise
- React
- react-query
- Next.js
- MSW
- mocking
- d3
- Suspense
- WebSocket