Docs
Incremental Static Regeneration

Incremental Static Regeneration (ISR)

예제

Next.js를 사용하면 사이트를 빌드한 정적 페이지를 생성하거나 업데이트할 수 있습니다. Incremental Static Regeneration (ISR)을 사용하면 전체 사이트를 재빌드할 필요 없이 페이지별로 정적 생성을 사용할 수 있습니다. ISR을 사용하면 정적 페이지의 장점을 유지하면서 수백만 개의 페이지로 확장할 수 있습니다.

알아두면 좋은 점: edge runtime은 현재 ISR과 호환되지 않지만, cache-control 헤더를 수동으로 설정하여 stale-while-revalidate를 활용할 수 있습니다.

ISR을 사용하려면, revalidate prop을 getStaticProps에 추가하세요:

function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
 
// 이 함수는 빌드 시 서버 측에서 호출됩니다.
// 재검증이 활성화되고 새로운 요청이 들어오면,
// 서버리스 함수에서 다시 호출될 수 있습니다.
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()
 
  return {
    props: {
      posts,
    },
    // Next.js는 페이지 재생성을 시도합니다:
    // - 요청이 들어올 때
    // - 최대 10초마다 한 번씩
    revalidate: 10, // 초 단위
  }
}
 
// 이 함수는 빌드 시 서버 측에서 호출됩니다.
// 경로가 생성되지 않은 경우,
// 서버리스 함수에서 다시 호출될 수 있습니다.
export async function getStaticPaths() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()
 
  // 게시물을 기반으로 사전 렌더링할 경로를 가져옵니다.
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))
 
  // 빌드 시 이러한 경로만 사전 렌더링합니다
  // 경로가 존재하지 않는 경우, { fallback: 'blocking' }은
  // 온디맨드로 페이지를 서버 렌더링합니다.
  return { paths, fallback: 'blocking' }
}
 
export default Blog

빌드 시 사전 렌더링된 페이지에 요청을 하면 처음에는 캐시된 페이지가 표시됩니다.

  • 초기 요청 후 10초 이내에 발생한 페이지에 대한 모든 요청은 캐시되며 즉시 처리됩니다.
  • 10초가 지난 후의 다음 요청은 여전히 캐시된 (오래된) 페이지를 표시합니다.
  • Next.js는 백그라운드에서 페이지 재생성을 트리거합니다.
  • 페이지가 성공적으로 생성되면, Next.js는 캐시를 무효화하고 업데이트된 페이지를 표시합니다. 백그라운드 재생성이 실패하면, 이전 페이지가 그대로 유지됩니다.

생성되지 않은 경로로 요청이 들어오면, Next.js는 첫 번째 요청에 대해 페이지를 서버 렌더링합니다. 이후의 요청은 캐시에서 정적 파일을 제공합니다. Vercel의 ISR은 캐시를 전역적으로 유지하고 롤백을 처리합니다 (opens in a new tab).

알아두면 좋은 점: 업스트림 데이터 제공자가 기본적으로 캐싱을 활성화했는지 확인하세요. 비활성화해야 할 수도 있습니다(예: useCdn: false). 그렇지 않으면 재검증이 ISR 캐시를 업데이트하기 위해 새로운 데이터를 가져올 수 없습니다. 캐싱은 (요청된 엔드포인트에 대해) Cache-Control 헤더를 반환할 때 CDN에서 발생할 수 있습니다.

On-Demand Revalidation

revalidate 시간을 60으로 설정하면, 모든 방문자는 1분 동안 동일한 버전의 사이트를 보게 됩니다. 캐시를 무효화하는 유일한 방법은 1분이 지난 후 누군가 해당 페이지를 방문하는 것입니다.

v12.2.0부터 Next.js는 특정 페이지에 대한 Next.js 캐시를 수동으로 삭제할 수 있는 온디맨드 Incremental Static Regeneration을 지원합니다. 이를 통해 다음과 같은 경우에 사이트를 더 쉽게 업데이트할 수 있습니다:

  • 헤드리스 CMS의 콘텐츠가 생성되거나 업데이트된 경우
  • 전자상거래 메타데이터 변경 (가격, 설명, 카테고리, 리뷰 등)

getStaticProps 내부에서 온디맨드 재검증을 사용하기 위해 revalidate를 지정할 필요가 없습니다. revalidate를 생략하면, Next.js는 기본값인 false (재검증 없음)를 사용하고 revalidate()가 호출될 때만 온디맨드로 페이지를 재검증합니다.

알아두면 좋은 점: Middleware는 온디맨드 ISR 요청에 대해 실행되지 않습니다. 대신, 재검증하려는 정확한 경로에서 revalidate()를 호출하세요. 예를 들어 pages/blog/[slug].js가 있고 /post-1 -> /blog/post-1의 rewrite가 있는 경우, res.revalidate('/blog/post-1')를 호출해야 합니다.

Using On-Demand Revalidation

먼저 Next.js 앱에서만 알 수 있는 비밀 토큰을 생성합니다. 이 비밀 토큰은 재검증 API 라우트에 대한 무단 접근을 방지하는 데 사용됩니다. 다음 URL 구조로 (수동으로 또는 웹훅으로) 경로에 접근할 수 있습니다:

Terminal
https://<your-site.com>/api/revalidate?secret=<token>

다음으로, 애플리케이션에 비밀 토큰을 환경 변수로 추가합니다. 마지막으로, 재검증 API 라우트를 생성합니다:

pages/api/revalidate.js
export default async function handler(req, res) {
  // 이 요청이 유효한지 확인하기 위해 비밀 토큰을 확인합니다.
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: 'Invalid token' })
  }
 
  try {
    // 이 경로는 rewritten 경로가 아닌 실제 경로여야 합니다.
    // 예: "/blog/[slug]"의 경우 "/blog/post-1"이어야 합니다.
    await res.revalidate('/path-to-revalidate')
    return res.json({ revalidated: true })
  } catch (err) {
    // 오류가 발생한 경우
    // Next.js는 마지막으로 성공적으로 생성된 페이지를 계속 표시합니다.
    return res.status(500).send('Error revalidating')
  }
}

데모 (opens in a new tab)에서 온디맨드 재검증이 동작하는 것을 확인하고 피드백을 제공하세요.

Testing on-Demand ISR during development

next dev로 로컬에서 실행할 경우, 모든 요청에 대해 getStaticProps가 호출됩니다. 온디맨드 ISR 구성이 올바른지 확인하려면 프로덕션 빌드를 생성하고 프로덕션 서버를 시작해야 합니다:

Terminal
$ next build
$ next start

그런 다음, 정적 페이지가 성공적으로 재검증되었는지 확인할 수 있습니다.

Error handling and revalidation

백그라운드 재생성을 처리할 때 getStaticProps 내부에 오류가 발생하거나, 수동으로 오류를 발생시키면 마지막으로 성공적으로 생성된 페이지가 계속 표시됩니다. 다음 후속 요청 시, Next.js는 getStaticProps 호출을 다시 시도합니다.

export async function getStaticProps() {
  // 이 요청에서 예기치 않은 오류가 발생하면,
  // Next.js는 현재 표시된 페이지를 무효화하지 않고
  // 다음 요청 시 getStaticProps를 다시 시도합니다.
  const res = await fetch('https://.../posts')
  const posts = await res.json()
 
  if (!res.ok) {
    // 서버 오류가 발생한 경우,
    // 다음 요청이 성공할 때까지 캐시가 업데이트되지 않도록
    // 반환하는 대신 오류를 발생시키는 것이 좋습니다.
    throw new Error(`Failed to fetch posts, received status ${res.status}`)
  }
 
  // 요청이 성공하면 게시물을 반환하고
  // 10초마다 재검증합니다.
  return {
    props: {
      posts,
    },
    revalidate: 10,
  }
}

Self-hosting ISR

Incremental Static Regeneration (ISR)은 next start를 사용할 때 자체 호스팅된 Next.js 사이트에서 기본적으로 작동합니다.

Next.js 자체 호스팅에 대해 자세히 알아보세요.

Version History

버전변경 사항
v14.1.0커스텀 cacheHandler 안정화
v12.2.0온디맨드 ISR 안정화
v12.1.0온디맨드 ISR 추가 (베타)
v12.0.0Bot-aware ISR fallback (opens in a new tab) 추가
v9.5.0Base Path 추가