notes
Next.js ISR (Incremental Static Regeneration)
Next.js ISR의 정적 재생성·revalidate 동작과 캐시 갱신 메모.
💡 ISR은 Incremental Static Regeneration의 약자로, 빌드 시점에 페이지를 렌더링한 후 설정한 시간마다 페이지를 새로 렌더링하는 방식입니다. SSG에 포함되는 개념이지만, 데이터 변경 시 재빌드 없이 주기적으로 업데이트할 수 있다는 점이 핵심입니다.
📑 목차
- SSG vs ISR vs SSR 비교
- ISR 동작 원리
- Pages Router에서 ISR 사용하기
- App Router에서 ISR 사용하기
- On-Demand Revalidation
- 플랫폼 지원 및 제한사항
- 참고 문서
📊 SSG vs ISR vs SSR 비교
핵심 차이
- SSG → 데이터 변경 시 전체 사이트 재빌드 필요
- ISR →
revalidate시간(초)마다 백그라운드 자동 갱신, 또는revalidatePath/revalidateTag로 수동 갱신 - SSR → 항상 최신 데이터, 하지만 TTFB 증가 및 서버 부하 증가
SSG vs ISR 타임라인
⚙️ ISR 동작 원리
흐름도
위 다이어그램은 ISR의 요청 처리 흐름을 단순화한 것입니다. (다이어그램 레이블은 호환성을 위해 영문으로 표기)
단계별 설명
1. 최초 요청
- 페이지가 캐시에 없으면 서버에서 렌더링 후 캐시에 저장
- fallback: 'blocking' 사용 시 첫 요청에서 생성
2. revalidate 이내 요청 - 캐시된 HTML을 즉시 반환 (빠른 응답)
3. revalidate 경과 후 첫 요청 - 기존 캐시(Stale)를 그대로 반환 - 백그라운드에서 새 페이지 생성 시작
4. 재생성 완료 - 성공 시 캐시 갱신, 이후 요청은 새 페이지 제공 - 실패 시 기존 페이지 유지, 다음 요청에서 재시도
📄 Pages Router에서 ISR 사용하기
기본 예시
getStaticProps에 revalidate를 추가합니다.
// pages/blog/[id].tsx
import type { GetStaticPaths, GetStaticProps } from 'next'
interface Post {
id: string
title: string
content: string
}
export const getStaticPaths = async () => {
const posts = await fetch('https://api.example.com/posts').then((r) => r.json())
const paths = posts.map((post: Post) => ({ params: { id: post.id } }))
return { paths, fallback: 'blocking' }
}
export const getStaticProps: GetStaticProps<{ post: Post }> = async ({ params }) => {
const post = await fetch(`https://api.example.com/posts/${params?.id}`).then(
(r) => r.json()
)
return {
props: { post },
revalidate: 60, // 60초마다 최대 1회 백그라운드 갱신
}
}
export default function BlogPost({ post }: { post: Post }) {
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
fallback 옵션
📱 App Router에서 ISR 사용하기
App Router에서는 fetch의 next.revalidate 또는 세그먼트의 revalidate export를 사용합니다.
1. fetch 옵션으로 시간 기반 갱신
// app/blog/page.tsx
export default async function BlogPage() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 }, // 60초마다 갱신
})
const posts = await res.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
2. 세그먼트 config로 페이지 전체 갱신
// app/products/page.tsx
export const revalidate = 3600 // 1시간
export default async function ProductsPage() {
const products = await getProducts()
return <ProductList products={products} />
}
3. 태그 + 시간 기반 조합
const res = await fetch('https://api.example.com/products', {
next: {
revalidate: 3600,
tags: ['products'],
},
})
🔄 On-Demand Revalidation
revalidate 시간을 기다리지 않고, 특정 시점에 캐시를 무효화할 수 있습니다.
Pages Router: API Route + res.revalidate()
// pages/api/revalidate.ts
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.query.secret !== process.env.REVALIDATION_SECRET) {
return res.status(401).json({ message: 'Invalid token' })
}
try {
await res.revalidate('/blog/1')
return res.json({ revalidated: true })
} catch (err) {
return res.status(500).send('Error revalidating')
}
}
CMS 웹훅 등에서 /api/revalidate?secret=xxx 호출로 해당 경로만 갱신할 수 있습니다.
App Router: revalidatePath / revalidateTag
// app/actions.ts
'use server'
import { revalidatePath, revalidateTag } from 'next/cache'
// 특정 경로 갱신
export async function revalidateBlogPost(path: string) {
revalidatePath(path)
}
// 특정 태그가 붙은 fetch 모두 갱신
export async function revalidateProducts() {
revalidateTag('products')
}
// Route Handler에서 사용
import { revalidatePath } from 'next/cache'
import { NextRequest } from 'next/server'
export async function POST(request: NextRequest) {
const body = await request.json()
// CMS 웹훅 등에서 호출
revalidatePath(`/blog/${body.slug}`)
return Response.json({ ok: true })
}
⚠️ 플랫폼 지원 및 제한사항
지원 환경
주의사항
- Static Export (
output: 'export')에서는 ISR 미지원 - Node.js 런타임에서만 동작 (Edge 런타임 제한)
- On-Demand ISR 시 Proxy/rewrite가 적용되지 않으므로, 실제 경로를 사용해야 함 (예:
/posts/1)
디버깅
# .env
NEXT_PRIVATE_DEBUG_CACHE=1
next build 후 next start로 실행하면 캐시 hit/miss 로그를 확인할 수 있습니다.
📚 참고 문서
- Next.js 공식 - Incremental Static Regeneration (Pages)
- Next.js 공식 - Fetching, Caching, and Revalidating (App)
- Next.js - revalidatePath
- Next.js - revalidateTag
- Vercel - ISR