В современных веб-приложениях различные страницы имеют принципиально разные требования к скорости отдачи, актуальности контента и нагрузке на инфраструктуру:
Next.js App Router объединяет эти сценарии в единую гибридную систему рендеринга. Вместо устаревшего разделения на уровне страниц (getServerSideProps / getStaticProps из Pages Router), в современной архитектуре стратегия рендеринга определяется декларативно на уровне маршрутов (Route Segments) и отдельных вызовов данных (Data Cache).
В Next.js App Router существует три базовые стратегии рендеринга:
cookies(), headers(), searchParams) или принудительно через export const dynamic = 'force-dynamic'.Клиент A Edge CDN Фоновый Воркер (Node.js)
│ │ │
(1) ├──── GET /product/42 ───────►│ │
│ │ [Кэш свежий: age < 60s] │
│◄─── 200 OK (HTML/RSC) ──────┤ │
│ │ │
═══════╪═════════════════════════════╪═════════════════════════════════╪═══════
│ (Прошло 65 секунд: кэш стал STALE) │
│ │ │
(2) │ Клиент B │ │
├──── GET /product/42 ───────►│ │
│ │ [Кэш устарел! Отдаем stale] │
│◄─── 200 OK (STALE копия) ───┤ │
│ │ │
│ ├── Асинхронный триггер рендера ─►│
│ │ ├── Запрос к БД
│ │ ├── Генерация HTML/RSC
│ │◄── Новый HTML/RSC Payload ──────┤
│ │ [Кэш обновлен: age = 0s] │
│ │ │
(3) │ Клиент C │ │
├──── GET /product/42 ───────►│ │
│ │ [Кэш свежий!] │
│◄─── 200 OK (СВЕЖИЕ данные) ─┤ │Страница рендерится при сборке (SSG) или по требованию первого пользователя. Сгенерированный HTML и RSC Flight сохраняются в Full Route Cache и Edge CDN.
В течение заданного времени (например, revalidate = 60) все пользователи мгновенно получают ответ из кэша CDN со сверхнизким TTFB.
По истечении 60 секунд кэш помечается как Stale (устаревший). При следующем запросе CDN мгновенно отдает клиенту старую копию без ожидания сервера.
Next.js параллельно инициирует изолированный фоновый процесс рендеринга страницы с обращением к источникам данных.
После успешной сборки новый HTML и RSC Payload атомарно перезаписывают кэш. Все последующие запросы получают обновленный контент.
Что произойдет с пользователем, который запросил страницу со сроком revalidate = 60 секунд спустя 65 секунд после её последней генерации?
Поведение страницы можно явно задать с помощью экспорта специальных констант из page.tsx или layout.tsx:
// 1. Принудительный Dynamic SSR (никакого кэширования страницы)
export const dynamic = 'force-dynamic';
// 2. Принудительный SSG (динамические функции вернут пустые значения)
// export const dynamic = 'force-static';
// 3. Time-based ISR: ревалидировать страницу не чаще одного раза в 60 секунд
// export const revalidate = 60;
// 4. Поведение для параметров, не сгенерированных в generateStaticParams:
// true: генерировать динамически при первом запросе и сохранять в ISR (по умолчанию)
// false: возвращать 404
export const dynamicParams = true;
export default async function DashboardPage() {
return <div>Динамическая панель управления</div>;
}fetch() в Next.js 15+#[!IMPORTANT] Архитектурный стандарт: Начиная с Next.js 15, глобальный вызов
fetch()по умолчанию НЕ кэшируется (cache: 'no-store'). Это прямо противоположно поведению Next.js 13/14!
Для управления кэшем отдельных сетевых запросов передавайте параметры в объекте конфигурации fetch:
// 1. SSG: Бессрочное кэширование (Permanent Data Cache)
const staticData = await fetch('https://api.example.com/config', {
cache: 'force-cache'
});
// 2. Time-based ISR: Кэширование с инвалидацией по времени (1 час)
const timedData = await fetch('https://api.example.com/products', {
next: {
revalidate: 3600,
tags: ['products-list'] // Тег для сброса по требованию
}
});
// 3. SSR: Запрос без кэширования (поведение Next.js 15+ по умолчанию)
const liveData = await fetch('https://api.example.com/notifications', {
cache: 'no-store'
});revalidateTag и revalidatePath#Сброс кэша по событию (например, при сохранении товара в админке или по входящему вебхуку из CMS) выполняется на сервере через Server Actions или Route Handlers:
'use server';
import { revalidateTag, revalidatePath } from 'next/cache';
import { db } from '@/lib/db';
interface UpdateProductDTO {
title: string;
price: number;
}
export async function updateProduct(id: string, data: UpdateProductDTO): Promise<{ success: boolean }> {
// 1. Мутация данных в БД
await db.product.update({
where: { id },
data
});
// 2. Инвалидация конкретного кэш-тега (точечно сбрасывает связанные запросы)
revalidateTag(`product-${id}`);
// 3. Инвалидация маршрута каталога
revalidatePath('/catalog', 'page');
return { success: true };
}Если данные запрашиваются через ORM (Prisma, Drizzle, TypeORM), вызов fetch отсутствует. В Next.js 16 для прямого кэширования серверных функций и компонентов используется директива 'use cache' с хелперами cacheLife и cacheTag:
import 'server-only';
import { cacheLife, cacheTag, unstable_cache } from 'next/cache';
import { db } from '@/lib/db';
// 1. Стандарт Next.js 16: директива 'use cache' с декларативными тегами и TTL
export async function getCachedPost(slug: string) {
'use cache';
cacheLife('minutes'); // Пресет времени жизни: seconds, minutes, hours, days, max
cacheTag('posts', `post-${slug}`);
return await db.post.findUnique({
where: { slug },
});
}
// 2. Обратная совместимость с Next.js 14/15 через unstable_cache
export const getCachedPostLegacy = unstable_cache(
async (slug: string) => {
return await db.post.findUnique({
where: { slug },
});
},
['post-by-slug'],
{
revalidate: 300,
tags: ['posts'],
}
);В интернет-магазине с 200 000 товаров предварительно собирать все страницы нецелесообразно. Паттерн объединяет generateStaticParams для топ-100 товаров и автоматический фоновый ISR для всех остальных:
import { notFound } from 'next/navigation';
import { db } from '@/lib/db';
interface PageProps {
params: Promise<{ id: string }>; // В Next.js 15+ доступ строго асинхронный
}
// 1. Предварительная сборка топ-100 популярных страниц при билде
export async function generateStaticParams() {
const popularProducts = await db.product.findMany({
where: { isPopular: true },
take: 100,
select: { id: true }
});
return popularProducts.map((p) => ({ id: p.id }));
}
// 2. Разрешаем генерацию остальных 199 900 товаров на лету при первом визите
export const dynamicParams = true;
// 3. Обновлять страницу в фоне не чаще раза в 10 минут
export const revalidate = 600;
export default async function ProductPage({ params }: PageProps) {
const { id } = await params;
const product = await db.product.findUnique({ where: { id } });
if (!product) {
notFound();
}
return (
<article className="p-8 max-w-3xl mx-auto">
<h1 className="text-3xl font-bold">{product.title}</h1>
<p className="text-2xl font-mono text-accent-cyan mt-2">{product.price} ₽</p>
<div className="mt-4 text-text-muted leading-relaxed">{product.description}</div>
</article>
);
}import { NextRequest, NextResponse } from 'next/server';
import { revalidateTag } from 'next/cache';
export async function POST(request: NextRequest) {
const secret = request.nextUrl.searchParams.get('secret');
// Защита эндпоинта секретным токеном
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ message: 'Неверный токен' }, { status: 401 });
}
const body = (await request.json()) as { tag?: string };
if (!body.tag) {
return NextResponse.json({ message: 'Тег не передан' }, { status: 400 });
}
// Мгновенный сброс Data Cache для всех запросов с данным тегом
revalidateTag(body.tag);
return NextResponse.json({ revalidated: true, tag: body.tag, now: Date.now() });
}Какая команда наиболее эффективно обновит только блок рекомендованных товаров без пересборки всей HTML-страницы каталога?
В Next.js 13 и 14 любые вызовы fetch() автоматически мемоизировались и кэшировались со стратегией force-cache. Разработчики привыкли, что страницы со статическими fetch генерируются в SSG.
В Next.js 15+ семантика изменилась на противоположную: fetch() по умолчанию выполняется с опцией cache: 'no-store'. Если вы напишете await fetch('https://api.com/items') без явных настроек кэша, Next.js сделает страницу динамической (SSR), и она будет заново запрашивать API при каждом визите пользователя!
Решение: Всегда явно объявляйте стратегию кэширования (force-cache для SSG или next: { revalidate: 60 } для ISR).
// Для SSG:
const res = await fetch(url, { cache: 'force-cache' });
// Для ISR:
const res = await fetch(url, { next: { revalidate: 60 } });Если в любом (даже глубоко вложенном) серверном компоненте вызвать динамическую функцию await cookies() или await headers(), Next.js вынужден прервать статическую сборку и перевести весь маршрут в режим Dynamic SSR.
Решение:
<Suspense fallback={<CartSkeleton />}> — в этом случае каркас страницы останется в SSG/ISR, а динамический слот достримится клиенту.// components/CartBadge.tsx (Server Component в сайдбаре)
import { cookies } from 'next/headers';
export async function CartBadge() {
const cookieStore = await cookies(); // ❌ Ломает SSG/ISR для всего сайта без Suspense!
const cartId = cookieStore.get('cartId')?.value;
return <span>{cartId ? 'Товаров: 1' : 'Корзина пуста'}</span>;
}Когда истекает таймер revalidate: 60, первая входящая сессия инициирует фоновую перегенерацию страницы. Если серверная функция рендеринга выполняется 3-5 секунд, а на сайт поступает 500 RPS, без распределенных блокировок может возникнуть ситуация, когда десятки фоновых воркеров параллельно нагружают базу данных тяжелыми SQL-запросами.
Решение на уровне архитектуры: В Next.js встроен механизм дедупликации регенерации (Stale-While-Revalidate lock), однако для прямых запросов к БД через ORM обязательно используйте unstable_cache с тегами и стратегией явной On-demand ревалидации (revalidateTag) по вебхуку вместо агрессивного time-based поллинга.
Вызов revalidatePath('/') очищает кэш только для главной страницы. Чтобы сбросить кэш для всего сайта целиком, необходимо передать тип 'layout': revalidatePath('/', 'layout'). Однако злоупотребление этой конструкцией в продакшене приводит к резким пикам нагрузки на БД, поскольку тысячи страниц инвалидируются одновременно. Предпочитайте точечный revalidateTag().