iBetter Books
수정

Ch 05. 정기적으로 새로 굽다 — ISR

매일 아침 새로 굽는 빵

앞 챕터에서 빵을 미리 구워두는 SSG를 이야기했습니다. 그런데 빵집에 문제가 생겼습니다. 새벽 네 시에 구운 빵이 오후 세 시까지도 그대로 있습니다. 아침에 구운 빵은 맛있었지만, 시간이 지날수록 신선함이 떨어집니다.

이 문제를 해결하는 방법이 있습니다. 매일 아침, 그리고 점심에 한 번 더, 저녁에도 한 번 — 정기적으로 빵을 새로 굽는 겁니다. 손님에게는 항상 최근에 구운 빵을 드릴 수 있습니다.

ISR(Incremental Static Regeneration)이 이런 방식입니다. 처음에는 SSG처럼 빌드 시점에 HTML을 만들지만, 설정한 시간이 지나면 백그라운드에서 조용히 페이지를 새로 생성합니다.

revalidate 옵션으로 정기 재생성

ISR을 활성화하는 방법은 간단합니다. fetchnext: { revalidate: 초 } 옵션을 추가하거나, 페이지 파일에 export const revalidate = 초를 선언합니다.

// 파일: app/posts/page.tsx
// 이 페이지는 1시간(3600초)마다 새로 생성됩니다.
export const revalidate = 3600;

interface Post {
  id: number;
  title: string;
  excerpt: string;
  publishedAt: string;
}

async function getPosts(): Promise<Post[]> {
  const res = await fetch('https://api.example.com/posts', {
    next: { revalidate: 3600 }, // 3600초마다 재검증
  });
  return res.json();
}

export default async function PostsPage() {
  const posts = await getPosts();

  return (
    <main>
      <h1>블로그</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.excerpt}</p>
            <time>{post.publishedAt}</time>
          </li>
        ))}
      </ul>
    </main>
  );
}
```text

`revalidate: 3600`이면, 마지막으로 페이지를 생성한 지 1시간이 지난 후 첫 번째 요청이 오면 백그라운드에서 새 페이지를 생성하기 시작합니다. 그 사이 요청에는 이전에 만들어진 페이지를 계속 내려줍니다. 새 페이지 생성이 완료되면, 이후 요청부터 새 페이지를 받습니다.

### On-demand Revalidation — 원할 때 즉시 갱신

시간 기반 ISR의 한계는 최대 `revalidate` 초만큼 오래된 데이터가 보일 수 있다는 점입니다. 게시글을 방금 수정했는데 사용자에게는 1시간 후에나 반영된다면 불편합니다.

Next.js는 이를 해결하기 위해 **On-demand Revalidation**을 제공합니다. 특정 이벤트가 발생했을 때(게시글 저장, 댓글 작성 등) 캐시를 즉시 무효화할 수 있습니다.

```tsx
// 파일: app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const secret = searchParams.get('secret');

  // 비밀 토큰 검증
  if (secret !== process.env.REVALIDATE_SECRET) {
    return NextResponse.json({ error: '권한 없음' }, { status: 401 });
  }

  const path = searchParams.get('path') ?? '/posts';

  revalidatePath(path);

  return NextResponse.json({ revalidated: true, path });
}

이 API를 CMS나 웹훅에서 호출하면, 해당 경로의 캐시가 즉시 무효화됩니다. 다음 요청에서는 새로운 페이지가 생성됩니다.

SSG와의 차이

SSG는 빌드 이후에는 절대 변하지 않습니다. 새 배포를 해야만 반영됩니다. ISR은 빌드 이후에도 정해진 주기로, 또는 필요할 때 즉시 페이지를 새로 만들 수 있습니다.

구분 SSG ISR
최초 생성 빌드 시점 빌드 시점
이후 갱신 재배포 필요 자동 (주기적) 또는 수동 트리거
실시간성 없음 제한적 있음
서버 부하 없음 매우 낮음

다음 챕터에서는

다음 챕터에서는 네 가지 렌더링 방식을 한눈에 비교하고, 실무에서 어떤 페이지에 어떤 방식을 쓸지 결정하는 가이드를 정리합니다. 블로그 글 목록은 SSG가 맞을까요, ISR이 맞을까요? 답을 함께 찾아봅니다.