{post.title}
{post.content}
Vercel에서 Incremental Static Regeneration (ISR)을 설정하고 활용하는 방법을 다룹니다.
ISR(Incremental Static Regeneration)은 Next.js의 강력한 기능으로, 정적 페이지를 동적으로 업데이트할 수 있게 해줍니다. Vercel에서 ISR을 설정하는 방법을 살펴보겠습니다.
npm list next
# next@14.1.0
# Vercel 환경
vercel --version
# 33.0.0정적 페이지의 콘텐츠가 업데이트되지 않아 사용자에게 최신 정보를 제공할 수 없었습니다:
# 기존 정적 생성
getStaticProps → 빌드 시점의 데이터
# 문제점
- 새 글 발행 후 재배포 필요
- 실시간 데이터 반영 불가
- 빈번한 배포로 인한 지연요청 → 캐시된 페이지 반환
│
├─ 재생성 시간 미경과 → 캐시 제공
│
└─ 재생성 시간 경과 → 새 페이지 생성 후 캐시| 전략 | 장점 | 단점 |
|---|---|---|
| SSG | 빠른 속도 | 업데이트 어려움 |
| SSR | 실시간 데이터 | 느린 속도 |
| ISR | 둘 다 장점 | 설정 복잡 |
// pages/blog/[id].js
export async function getStaticProps({ params }) {
const post = await fetchPost(params.id);
return {
props: { post },
revalidate: 60, // 60초마다 재생성
};
}
export async function getStaticPaths() {
const posts = await fetchAllPosts();
return {
paths: posts.map(post => ({
params: { id: post.id.toString() },
})),
fallback: 'blocking',
};
}
export default function BlogPost({ post }) {
return (
{post.title}
{post.content}
);
}// pages/products/[id].js
export async function getStaticProps({ params }) {
const product = await fetchProduct(params.id);
if (!product) {
return { notFound: true };
}
return {
props: { product },
revalidate: product.stock > 0 ? 30 : 300,
};
}// pages/api/revalidate.js
import { revalidatePath } from 'next/cache';
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { path, secret } = req.body;
if (secret !== process.env.REVALIDATION_SECRET) {
return res.status(401).json({ error: 'Invalid secret' });
}
try {
revalidatePath(path);
return res.json({ revalidated: true, now: Date.now() });
} catch (error) {
return res.status(500).json({ error: 'Revalidation failed' });
}
}// vercel.json
{
"functions": {
"pages/blog/[id].js": {
"maxDuration": 30
}
},
"regions": ["iad1"]
}// pages/api/revalidate-batch.js
import { revalidatePath } from 'next/cache';
export default async function handler(req, res) {
const { paths, secret } = req.body;
if (secret !== process.env.REVALIDATION_SECRET) {
return res.status(401).json({ error: 'Invalid secret' });
}
const results = [];
for (const path of paths) {
try {
revalidatePath(path);
results.push({ path, status: 'success' });
} catch (error) {
results.push({ path, status: 'error', error: error.message });
}
}
return res.json({ results });
}// pages/blog/[id].js
export async function getStaticProps({ params }) {
const post = await fetchPost(params.id);
return {
props: { post },
revalidate: 60,
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
},
};
}// lib/monitoring.js
export function logISRRevalidation(path, duration) {
console.log(JSON.stringify({
event: 'isr_revalidation',
path,
duration: `${duration}ms`,
timestamp: new Date().toISOString(),
}));
}
// pages/blog/[id].js
export async function getStaticProps({ params }) {
const start = Date.now();
const post = await fetchPost(params.id);
const duration = Date.now() - start;
logISRRevalidation(`/blog/${params.id}`, duration);
return {
props: { post },
revalidate: 60,
};
}blocking을 사용하여 사용자에게 항상 최신 콘텐츠를 제공하세요stale-while-revalidate로 성능과 신선도를 동시에 확보하세요This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.