{data.title}
{data.content}
Next.js ISR(Incremental Static Regeneration)을 Vercel에서 설정하고 최적화하는 방법을 알아봅니다.
ISR(Incremental Static Regeneration)은 Next.js의 강력한 기능으로, 정적 페이지를 동적으로 업데이트할 수 있습니다. Vercel 환경에서 ISR을 설정하고 최적화하는 방법을 알아보겠습니다.
ISR 설정이 올바르지 않으면 페이지가 예상대로 업데이트되지 않습니다:
// 페이지가 업데이트되지 않는 문제
export async function getStaticProps() {
const data = await fetchData();
return {
props: { data },
revalidate: 60 // 60초마다 재검증
};
}
// 하지만 실제로는 페이지가 업데이트되지 않음ISR의 동작 원리:
on-demand 재검증 가능// app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(res => res.json());
return posts.map((post) => ({
slug: post.slug,
}));
}
export default async function BlogPost({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`);
const data = await post.json();
return (
{data.title}
{data.content}
);
}
export const revalidate = 60; // 60초마다 재검증// app/api/revalidate/route.js
import { NextResponse } from 'next/server';
import { revalidatePath, revalidateTag } from 'next/cache';
export async function POST(request) {
const { path, tag, secret } = await request.json();
// 시크릿 키 확인
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ error: 'Invalid secret' }, { status: 401 });
}
try {
if (tag) {
revalidateTag(tag);
return NextResponse.json({ revalidated: true, type: 'tag', tag });
}
if (path) {
revalidatePath(path);
return NextResponse.json({ revalidated: true, type: 'path', path });
}
return NextResponse.json({ error: 'Path or tag required' }, { status: 400 });
} catch (error) {
return NextResponse.json({ error: 'Revalidation failed' }, { status: 500 });
}
}// app/blog/page.js
import { Suspense } from 'react';
async function BlogList() {
const posts = await fetch('https://api.example.com/posts', {
next: { tags: ['blog'] } // 태그 설정
}).then(res => res.json());
return (
{posts.map(post => (
-
{post.title}
))}
);
}
export default function BlogPage() {
return (
Blog
Loading... // lib/revalidation.js
export async function triggerRevalidation(path, tag) {
const response = await fetch(`${process.env.VERCEL_URL}/api/revalidate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
path,
tag,
secret: process.env.REVALIDATION_SECRET,
}),
});
return response.json();
}
// CMS 웹훅 핸들러
// app/api/webhook/cms/route.js
import { NextResponse } from 'next/server';
import { triggerRevalidation } from '@/lib/revalidation';
export async function POST(request) {
const { event, data } = await request.json();
switch (event) {
case 'post.created':
case 'post.updated':
await triggerRevalidation(`/blog/${data.slug}`, 'blog');
break;
case 'post.deleted':
await triggerRevalidation('/blog', 'blog');
break;
}
return NextResponse.json({ success: true });
}// app/products/[id]/page.js
export default async function ProductPage({ params }) {
const product = await fetch(`https://api.example.com/products/${params.id}`);
const data = await product.json();
return (
{data.name}
{data.price}
);
}
// 상품별 다른 재검증 시간 설정
export async function generateStaticParams() {
const products = await fetch('https://api.example.com/products').then(res => res.json());
return products.map((product) => ({
id: product.id.toString(),
}));
}
export const revalidate = false; // 동적 렌더링 사용
// 또는 조건부 재검증
export async function getStaticProps({ params }) {
const product = await fetch(`https://api.example.com/products/${params.id}`);
const data = await product.json();
// 상품 유형에 따라 재검증 시간 결정
const revalidateTime = data.type === 'digital' ? 3600 : 86400;
return {
props: { product: data },
revalidate: revalidateTime,
};
}// lib/isr-monitor.js
export async function checkISRStatus() {
const response = await fetch(`${process.env.VERCEL_URL}/api/revalidation/status`);
const status = await response.json();
console.log('ISR Status:');
console.log(`Last revalidation: ${status.lastRevalidation}`);
console.log(`Next revalidation: ${status.nextRevalidation}`);
console.log(`Revalidated paths: ${status.paths.length}`);
return status;
}
// ISR 히스토리 추적
export function trackISRHistory(path, timestamp) {
const history = JSON.parse(localStorage.getItem('isr-history') || '[]');
history.push({ path, timestamp });
localStorage.setItem('isr-history', JSON.stringify(history.slice(-100)));
}# .env.local
REVALIDATION_SECRET=your-secret-key
VERCEL_URL=https://your-app.vercel.appThis blog does not accept any external sponsorships, affiliate marketing, or ad revenue.