Введение/Руководства/ISR

Как реализовать инкрементальную статическую регенерацию (ISR)

Примеры

Инкрементальная статическая регенерация (ISR) позволяет:

  • Обновлять статический контент без пересборки всего сайта
  • Снижать нагрузку на сервер, обслуживая предварительно отрендеренные статические страницы для большинства запросов
  • Гарантировать автоматическое добавление правильных заголовков cache-control к страницам
  • Обрабатывать большое количество страниц контента без длительного времени сборки next build

Минимальный пример:

import type { GetStaticPaths, GetStaticProps } from 'next'

interface Post {
  id: string
  title: string
  content: string
}

interface Props {
  post: Post
}

export const getStaticPaths: GetStaticPaths = async () => {
  const posts = await fetch('https://api.vercel.app/blog').then((res) =>
    res.json()
  )
  const paths = posts.map((post: Post) => ({
    params: { id: String(post.id) },
  }))

  // Мы предварительно рендерим только эти пути во время сборки.
  // { fallback: 'blocking' } выполнит серверный рендеринг страниц
  // по требованию, если путь не существует.
  return { paths, fallback: false }
}

export const getStaticProps: GetStaticProps<Props> = async ({
  params,
}: {
  params: { id: string }
}) => {
  const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
    (res) => res.json()
  )

  return {
    props: { post },
    // Next.js будет инвалидировать кеш при 
    // поступлении запроса, максимум раз в 60 секунд.
    revalidate: 60,
  }
}

export default function Page({ post }: Props) {
  return (
    <main>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </main>
  )
}
export async function getStaticPaths() {
  const posts = await fetch('https://api.vercel.app/blog').then((res) =>
    res.json()
  )
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // Мы предварительно рендерим только эти пути во время сборки.
  // { fallback: false } означает, что другие маршруты должны возвращать 404.
  return { paths, fallback: false }
}

export async function getStaticProps({ params }) {
  const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
    (res) => res.json()
  )

  return {
    props: { post },
    // Next.js будет инвалидировать кеш при 
    // поступлении запроса, максимум раз в 60 секунд.
    revalidate: 60,
  }
}

export default function Page({ post }) {
  return (
    <main>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </main>
  )
}

Как работает этот пример:

  1. Во время next build генерируются все известные посты блога (в этом примере их 25)
  2. Все запросы к этим страницам (например, /blog/1) кешируются и обслуживаются мгновенно
  3. После истечения 60 секунд следующий запрос всё равно покажет закешированную (устаревшую) страницу
  4. Кеш инвалидируется, и в фоне начинается генерация новой версии страницы
  5. После успешной генерации Next.js отобразит и закеширует обновлённую страницу
  6. Если запрошен /blog/26, Next.js сгенерирует и закеширует эту страницу по требованию

Справочник

Функции

Примеры

Инвалидация по требованию с res.revalidate()

Для более точного метода регенерации используйте res.revalidate для генерации новой страницы по требованию из API Route.

Например, этот API Route можно вызвать по /api/revalidate?secret=<token>, чтобы регенерировать определённый пост блога. Создайте секретный токен, известный только вашему приложению Next.js. Этот секрет предотвратит несанкционированный доступ к API Route.

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // Проверка секрета для подтверждения валидности запроса
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: 'Неверный токен' })
  }

  try {
    // Должен быть фактический путь, а не переписанный
    // Например, для "/posts/[id]" это должен быть "/posts/1"
    await res.revalidate('/posts/1')
    return res.json({ revalidated: true })
  } catch (err) {
    // При ошибке Next.js продолжит показывать
    // последнюю успешно сгенерированную страницу
    return res.status(500).send('Ошибка регенерации')
  }
}
export default async function handler(req, res) {
  // Проверка секрета для подтверждения валидности запроса
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: 'Неверный токен' })
  }

  try {
    // Должен быть фактический путь, а не переписанный
    // Например, для "/posts/[id]" это должен быть "/posts/1"
    await res.revalidate('/posts/1')
    return res.json({ revalidated: true })
  } catch (err) {
    // При ошибке Next.js продолжит показывать
    // последнюю успешно сгенерированную страницу
    return res.status(500).send('Ошибка регенерации')
  }
}

При использовании инвалидации по требованию не нужно указывать время revalidate в getStaticProps. Next.js будет использовать значение по умолчанию false (без регенерации) и регенерировать страницу только по требованию при вызове res.revalidate().

Обработка неперехваченных исключений

При ошибке в getStaticProps во время фоновой регенерации или при ручном выбрасывании ошибки будет продолжать показываться последняя успешно сгенерированная страница. При следующем запросе Next.js повторит вызов getStaticProps.

import type { GetStaticProps } from 'next'

interface Post {
  id: string
  title: string
  content: string
}

interface Props {
  post: Post
}

export const getStaticProps: GetStaticProps<Props> = async ({
  params,
}: {
  params: { id: string }
}) => {
  // Если этот запрос выбросит неперехваченную ошибку, Next.js
  // не инвалидирует текущую страницу и
  // повторит getStaticProps при следующем запросе.
  const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
  const post: Post = await res.json()

  if (!res.ok) {
    // При ошибке сервера можно выбросить ошибку вместо возврата,
    // чтобы кеш не обновлялся до следующего успешного запроса.
    throw new Error(`Не удалось получить посты, статус ${res.status}`)
  }

  return {
    props: { post },
    // Next.js будет инвалидировать кеш при 
    // поступлении запроса, максимум раз в 60 секунд.
    revalidate: 60,
  }
}
export async function getStaticProps({ params }) {
  // Если этот запрос выбросит неперехваченную ошибку, Next.js
  // не инвалидирует текущую страницу и
  // повторит getStaticProps при следующем запросе.
  const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
  const post = await res.json()

  if (!res.ok) {
    // При ошибке сервера можно выбросить ошибку вместо возврата,
    // чтобы кеш не обновлялся до следующего успешного запроса.
    throw new Error(`Не удалось получить посты, статус ${res.status}`)
  }

  return {
    props: { post },
    // Next.js будет инвалидировать кеш при 
    // поступлении запроса, максимум раз в 60 секунд.
    revalidate: 60,
  }
}

Настройка расположения кеша

Можно настроить расположение кеша Next.js, если нужно сохранять закешированные страницы и данные в постоянное хранилище или делиться кешем между несколькими контейнерами или экземплярами приложения. Подробнее.

Устранение проблем

Отладка закешированных данных в локальной разработке

При использовании API fetch можно добавить логирование для понимания, какие запросы кешируются. Подробнее о опции logging.

next.config.js
module.exports = {
  logging: {
    fetches: {
      fullUrl: true,
    },
  },
}

Проверка корректного поведения в production

Чтобы убедиться, что ваши страницы правильно кэшируются и ревалидируются в production, вы можете протестировать это локально, выполнив команды next build, а затем next start для запуска production-сервера Next.js.

Это позволит вам проверить поведение ISR (инкрементальной статической регенерации) так, как оно работает в production-среде. Для дополнительной отладки добавьте следующую переменную окружения в ваш .env файл:

.env
NEXT_PRIVATE_DEBUG_CACHE=1

Это заставит сервер Next.js выводить в консоль информацию о попаданиях и промахах кэша ISR. Вы можете анализировать вывод, чтобы увидеть, какие страницы генерируются во время next build, а также как страницы обновляются при доступе к путям по требованию.

Ограничения

  • ISR поддерживается только при использовании Node.js runtime (по умолчанию).
  • ISR не поддерживается при создании статического экспорта.
  • Middleware не будет выполняться для запросов ISR по требованию, что означает, что любые перезаписи путей или логика в Middleware не будут применены. Убедитесь, что вы ревалидируете точный путь. Например, /post/1 вместо перезаписанного /post-1.

Поддержка платформ

Вариант развертыванияПоддерживается
Node.js серверДа
Docker контейнерДа
Статический экспортНет
АдаптерыЗависит от платформы

Узнайте, как настроить ISR при самостоятельном хостинге Next.js.

История версий

ВерсияИзменения
v14.1.0Пользовательский cacheHandler стал стабильным.
v13.0.0Добавлен App Router.
v12.2.0Pages Router: On-Demand ISR стал стабильным
v12.0.0Pages Router: Добавлен Bot-aware ISR fallback.
v9.5.0Pages Router: Добавлена стабильная ISR.