Как реализовать инкрементальную статическую регенерацию (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>
)
}
Как работает этот пример:
- Во время
next build
генерируются все известные посты блога (в этом примере их 25) - Все запросы к этим страницам (например,
/blog/1
) кешируются и обслуживаются мгновенно - После истечения 60 секунд следующий запрос всё равно покажет закешированную (устаревшую) страницу
- Кеш инвалидируется, и в фоне начинается генерация новой версии страницы
- После успешной генерации Next.js отобразит и закеширует обновлённую страницу
- Если запрошен
/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
.
module.exports = {
logging: {
fetches: {
fullUrl: true,
},
},
}
Проверка корректного поведения в production
Чтобы убедиться, что ваши страницы правильно кэшируются и ревалидируются в production, вы можете протестировать это локально, выполнив команды next build
, а затем next start
для запуска production-сервера Next.js.
Это позволит вам проверить поведение ISR (инкрементальной статической регенерации) так, как оно работает в production-среде. Для дополнительной отладки добавьте следующую переменную окружения в ваш .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.0 | Pages Router: On-Demand ISR стал стабильным |
v12.0.0 | Pages Router: Добавлен Bot-aware ISR fallback. |
v9.5.0 | Pages Router: Добавлена стабильная ISR. |