architecture2025-04-30·9·102/348

Vercel에서 ISR 설정 방법 완벽 가이드

Vercel에서 Incremental Static Regeneration (ISR)을 설정하고 활용하는 방법을 다룹니다.

Introduction

ISR(Incremental Static Regeneration)은 Next.js의 강력한 기능으로, 정적 페이지를 동적으로 업데이트할 수 있게 해줍니다. Vercel에서 ISR을 설정하는 방법을 살펴보겠습니다.

Environment

npm list next
# next@14.1.0

# Vercel 환경
vercel --version
# 33.0.0

Problem

정적 페이지의 콘텐츠가 업데이트되지 않아 사용자에게 최신 정보를 제공할 수 없었습니다:

# 기존 정적 생성
getStaticProps → 빌드 시점의 데이터

# 문제점
- 새 글 발행 후 재배포 필요
- 실시간 데이터 반영 불가
- 빈번한 배포로 인한 지연

Analysis

ISR 동작 방식

요청 → 캐시된 페이지 반환
  │
  ├─ 재생성 시간 미경과 → 캐시 제공
  │
  └─ 재생성 시간 경과 → 새 페이지 생성 후 캐시

빌드 전략 비교

전략장점단점
SSG빠른 속도업데이트 어려움
SSR실시간 데이터느린 속도
ISR둘 다 장점설정 복잡

Solution

1. 기본 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}

); }

2. 조건부 재생성

// 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,
    };
}

3. on-demand 재생성

// 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' });
    }
}

4. Vercel 설정

// vercel.json
{
    "functions": {
        "pages/blog/[id].js": {
            "maxDuration": 30
        }
    },
    "regions": ["iad1"]
}

5. 여러 경로 재생성

// 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 });
}

6. ISR 헤더 설정

// 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',
        },
    };
}

7. ISR 모니터링

// 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,
    };
}

Lessons Learned

  1. 적절한 revalidate 시간: 콘텐츠 변경 빈도에 따라 재생성 주기를 설정하세요
  2. fallback 설정: blocking을 사용하여 사용자에게 항상 최신 콘텐츠를 제공하세요
  3. on-demand 재생성: 중요 업데이트 시 on-demand 재생성을 활용하세요
  4. 캐시 전략: stale-while-revalidate로 성능과 신선도를 동시에 확보하세요
  5. 모니터링: ISR 재생성 시간을 모니터링하여 성능을 관리하세요

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.