Как работает Incremental Static Regeneration (ISR) в Next.js

ISR (Incremental Static Regeneration) это стратегия, которая объединяет преимущества SSG и SSR. Страница генерируется статически, но может обновляться в фоне через заданный интервал без полной пересборки приложения.

Проблема, которую решает ISR

Представь, что на Hack Frontend есть каталог задач. Он обновляется пару раз в день. SSG не подходит, потому что после добавления новой задачи нужно делать полный next build. SSR не подходит, потому что каталог одинаков для всех пользователей и нет смысла генерировать его при каждом запросе.

ISR решает эту дилемму: страница статическая, но умеет обновляться.

Как это работает

1

Первая сборка

Страница генерируется статически, как при SSG. HTML кешируется.

2

Запрос в пределах revalidate

Пользователь получает кешированную версию страницы. Никакого рендеринга на сервере.

3

Запрос после истечения revalidate

Пользователь все еще получает кешированную версию (stale), но Next.js запускает регенерацию в фоне. Следующий пользователь получит уже обновленную страницу.

Этот подход называется stale-while-revalidate: отдаем устаревшее, пока готовим свежее.

ISR в App Router

В App Router ISR настраивается через параметр revalidate в fetch:

// app/problems/page.tsx
export default async function ProblemsPage() {
  const res = await fetch('https://api.hackfrontend.com/problems', {
    next: { revalidate: 300 }
  })
  const problems = await res.json()

  return (
    <div>
      <h1>Задачи на Hack Frontend</h1>
      <ul>
        {problems.map((p: { id: string; name: string }) => (
          <li key={p.id}>{p.name}</li>
        ))}
      </ul>
    </div>
  )
}

revalidate: 300 значит: кешировать страницу на 5 минут, после чего обновить в фоне.

Настройка на уровне сегмента

Можно задать revalidate для всего роута:

// app/problems/layout.tsx
export const revalidate = 300

export default function ProblemsLayout({
  children
}: {
  children: React.ReactNode
}) {
  return <>{children}</>
}

On-Demand Revalidation

Вместо интервала можно обновлять страницу по событию. Например, после добавления новой задачи:

// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache'
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
  const { secret, path } = await request.json()

  if (secret !== process.env.REVALIDATION_SECRET) {
    return NextResponse.json({ error: 'Invalid secret' }, { status: 401 })
  }

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

Или с использованием тегов:

// Загрузка данных с тегом
const res = await fetch('https://api.hackfrontend.com/problems', {
  next: { tags: ['problems'] }
})

// Инвалидация по тегу после обновления данных
revalidateTag('problems')

На практике:

On-demand revalidation удобнее, чем интервальный подход. Ты обновляешь страницу именно тогда, когда данные изменились, а не через фиксированный промежуток.

ISR в Pages Router (Legacy)

В Pages Router ISR включается добавлением revalidate в getStaticProps:

// pages/problems.tsx
export async function getStaticProps() {
  const res = await fetch('https://api.hackfrontend.com/problems')
  const problems = await res.json()

  return {
    props: { problems },
    revalidate: 300
  }
}

Когда использовать ISR

Подходит:

  • Каталоги и списки (задачи, товары, курсы)
  • Контент, обновляемый несколько раз в день
  • Страницы, где допустима задержка в обновлении на несколько минут
  • Большие сайты, где полная пересборка занимает слишком много времени

Не подходит:

  • Персонализированный контент (нужен SSR)
  • Данные в реальном времени (нужен SSR или CSR с WebSocket)
  • Контент, который никогда не меняется (достаточно SSG)

Сравнение стратегий

SSGISRSSR
ГенерацияПри сборкеПри сборке + фоновое обновлениеПри каждом запросе
АктуальностьНа момент сборкиС задержкой (revalidate)Всегда актуальные
СкоростьМгновеннаяМгновеннаяЗависит от сервера
ПересборкаПолнаяНе нужнаНе нужна

Полезные ресурсы

Связанные темы