Реализация инкрементальной статической регенерации (ISR)
Инкрементальная статическая регенерация (ISR) позволяет:
- Обновлять статический контент без пересборки всего сайта
- Снижать нагрузку на сервер, обслуживая предварительно отрендеренные статические страницы для большинства запросов
- Гарантировать автоматическое добавление правильных заголовков
cache-control
к страницам - Обрабатывать большое количество страниц контента без длительного времени сборки
next build
Минимальный пример:
interface Post {
id: string
title: string
content: string
}
// Next.js будет инвалидировать кеш при
// поступлении запроса, максимум раз в 60 секунд.
export const revalidate = 60
// Мы предварительно рендерим только параметры из `generateStaticParams` во время сборки.
// Если поступит запрос для пути, который не был сгенерирован,
// Next.js выполнит серверный рендеринг страницы по требованию.
export const dynamicParams = true // или false, чтобы возвращать 404 для неизвестных путей
export async function generateStaticParams() {
const posts: Post[] = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post) => ({
id: String(post.id),
}))
}
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
const post: Post = await fetch(`https://api.vercel.app/blog/${id}`).then(
(res) => res.json()
)
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
// Next.js будет инвалидировать кеш при
// поступлении запроса, максимум раз в 60 секунд.
export const revalidate = 60
// Мы предварительно рендерим только параметры из `generateStaticParams` во время сборки.
// Если поступит запрос для пути, который не был сгенерирован,
// Next.js выполнит серверный рендеринг страницы по требованию.
export const dynamicParams = true // или false, чтобы возвращать 404 для неизвестных путей
export async function generateStaticParams() {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post) => ({
id: String(post.id),
}))
}
export default async function Page({ params }) {
const { id } = await params
const post = await fetch(`https://api.vercel.app/blog/${id}`).then((res) =>
res.json()
)
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
Как работает этот пример:
- Во время
next build
генерируются все известные посты блога (в этом примере их 25) - Все запросы к этим страницам (например,
/blog/1
) кешируются и обслуживаются мгновенно - После истечения 60 секунд следующий запрос всё равно покажет закешированную (устаревшую) страницу
- Кеш инвалидируется, и в фоне начинается генерация новой версии страницы
- После успешной генерации Next.js отобразит и закеширует обновлённую страницу
- Если запрошен
/blog/26
, Next.js сгенерирует и закеширует эту страницу по требованию
Справочник
Конфигурация сегмента маршрута
Функции
Примеры
Временная инвалидация
Этот код получает и отображает список постов блога на /blog
. Через час кеш для этой страницы инвалидируется при следующем посещении. Затем в фоне генерируется новая версия страницы с последними постами.
interface Post {
id: string
title: string
content: string
}
export const revalidate = 3600 // инвалидация каждый час
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts: Post[] = await data.json()
return (
<main>
<h1>Посты блога</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
)
}
export const revalidate = 3600 // инвалидация каждый час
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<main>
<h1>Посты блога</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
)
}
Рекомендуем устанавливать большое время инвалидации. Например, 1 час вместо 1 секунды. Если нужна более точная инвалидация, рассмотрите инвалидацию по требованию. Для данных в реальном времени используйте динамический рендеринг.
Инвалидация по требованию с revalidatePath
Для более точного метода инвалидации используйте функцию revalidatePath
.
Например, этот Server Action будет вызываться после добавления нового поста. Независимо от того, как вы получаете данные в Server Component (через fetch
или подключение к БД), это очистит кеш для всего маршрута и позволит Server Component получить свежие данные.
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// Инвалидирует маршрут /posts в кеше
revalidatePath('/posts')
}
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// Инвалидирует маршрут /posts в кеше
revalidatePath('/posts')
}
Посмотреть демо и исходный код.
Инвалидация по требованию с revalidateTag
В большинстве случаев лучше инвалидировать целые маршруты. Для более детального контроля используйте функцию revalidateTag
. Например, можно тегировать отдельные вызовы fetch
:
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog', {
next: { tags: ['posts'] },
})
const posts = await data.json()
// ...
}
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog', {
next: { tags: ['posts'] },
})
const posts = await data.json()
// ...
}
При использовании ORM или подключении к БД можно использовать unstable_cache
:
import { unstable_cache } from 'next/cache'
import { db, posts } from '@/lib/db'
const getCachedPosts = unstable_cache(
async () => {
return await db.select().from(posts)
},
['posts'],
{ revalidate: 3600, tags: ['posts'] }
)
export default async function Page() {
const posts = getCachedPosts()
// ...
}
import { unstable_cache } from 'next/cache'
import { db, posts } from '@/lib/db'
const getCachedPosts = unstable_cache(
async () => {
return await db.select().from(posts)
},
['posts'],
{ revalidate: 3600, tags: ['posts'] }
)
export default async function Page() {
const posts = getCachedPosts()
// ...
}
Затем можно использовать revalidateTag
в Server Actions или Route Handlers:
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// Инвалидирует все данные с тегом 'posts' в кеше
revalidateTag('posts')
}
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// Инвалидирует все данные с тегом 'posts' в кеше
revalidateTag('posts')
}
Обработка неперехваченных исключений
При ошибке во время попытки регенерации данных будут продолжать обслуживаться последние успешно сгенерированные данные из кеша. При следующем запросе Next.js повторит попытку регенерации. Подробнее об обработке ошибок.
Настройка расположения кеша
Можно настроить расположение кеша 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 не поддерживается при создании статического экспорта.
- Если у вас несколько запросов
fetch
в статически рендерящемся маршруте, и у каждого разная частотаrevalidate
, для ISR будет использовано наименьшее время. Однако эти частоты ревалидации по-прежнему будут учитываться кэшем данных. - Если любой из запросов
fetch
, используемых в маршруте, имеетrevalidate
время0
или явныйno-store
, маршрут будет рендериться динамически. - 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. |