SSR, SSG, ISR — три аббревиатуры, от которых у начинающих Next.js-разработчиков кружится голова. На самом деле всё просто: это три стратегии генерации страниц, каждая для своего сценария. Путать их — значит получить либо медленный сайт, либо устаревшие данные. Разбираемся, когда что использовать, с примерами кода для App Router.
Три стратегии в одной таблице
| Критерий | SSG | ISR | SSR |
|---|---|---|---|
| Когда генерируется | При сборке (build) | При сборке + обновляется по таймеру | На каждый запрос |
| Скорость ответа | Мгновенно (CDN) | Мгновенно (CDN, потом обновление) | Зависит от сервера |
| Свежесть данных | Данные на момент сборки | Обновляются через N секунд | Всегда свежие |
| SEO | Отлично | Отлично | Отлично |
| Нагрузка на сервер | Нулевая | Минимальная | Высокая |
| Подходит для | Лендинги, документация, блог | Каталоги, новости, контентные сайты | Личный кабинет, корзина, поиск |
SSG — Static Site Generation
Страница генерируется один раз при next build. После этого — статический HTML на CDN. Самый быстрый вариант.
В App Router все страницы по умолчанию статические, если не используют динамические функции (cookies, headers, searchParams).
// src/app/about/page.tsx
// Это SSG по умолчанию — нет динамических функций
export default function AboutPage() {
return (
<div>
<h1>О компании</h1>
Делаем сайты с 2020 года.
</div>
);
}
SSG с данными из CMS:
// src/app/blog/[slug]/page.tsx
// Генерируем список страниц заранее
export async function generateStaticParams() {
const posts = await prisma.post.findMany({
select: { slug: true },
});
return posts.map((post) => ({ slug: post.slug }));
}
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await prisma.post.findUnique({
where: { slug },
});
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
generateStaticParams — аналог старого getStaticPaths. Next.js при сборке вызовет его, получит все slug’и и сгенерирует HTML для каждого.
Когда использовать: страницы, которые не меняются без деплоя — «О компании», «Контакты», документация, статьи блога (если не обновляются часто).
ISR — Incremental Static Regeneration
ISR — это SSG с автообновлением. Страница отдаётся из кеша, но через заданный интервал перегенерируется в фоне. Пользователь всегда получает быстрый ответ, а данные постепенно обновляются.
// src/app/catalog/page.tsx
// Ревалидация каждые 60 секунд
export const revalidate = 60;
export default async function CatalogPage() {
const products = await prisma.product.findMany({
where: { published: true },
orderBy: { createdAt: "desc" },
take: 50,
});
return (
<div className="grid grid-cols-3 gap-4">
{products.map((product) => (
<div key={product.id} className="border rounded-lg p-4">
<h3>{product.title}</h3>
<span>{product.price} руб.</span>
</div>
))}
</div>
);
}
Как работает revalidate = 60:
- Первый запрос после сборки — отдаётся статический HTML
- Через 60 секунд следующий запрос всё ещё получает кешированную версию
- Но в фоне Next.js перегенерирует страницу с новыми данными
- Следующий посетитель получает обновлённую версию
Это стратегия stale-while-revalidate — пользователь никогда не ждёт.
On-Demand Revalidation — точечное обновление
Ждать 60 секунд не всегда приемлемо. Если контент-менеджер опубликовал статью — она должна появиться сразу. Для этого есть on-demand revalidation:
// src/app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from "next/cache";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const { secret, path, tag } = await request.json();
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ error: "Invalid secret" }, { status: 401 });
}
if (path) {
revalidatePath(path);
}
if (tag) {
revalidateTag(tag);
}
return NextResponse.json({ revalidated: true, now: Date.now() });
}
Вызов из CMS (например, webhook при публикации):
curl -X POST https://your-site.ru/api/revalidate \
-H "Content-Type: application/json" \
-d '{"secret": "your-secret", "path": "/blog"}'
Для использования тегов — добавляем их при запросе данных:
import { unstable_cache } from "next/cache";
const getProducts = unstable_cache(
async () => {
return prisma.product.findMany({ where: { published: true } });
},
["products"],
{ tags: ["products"], revalidate: 3600 }
);
Теперь revalidateTag("products") обновит все страницы, использующие эту функцию.
SSR — Server-Side Rendering
Страница генерируется на каждый запрос. Самый медленный вариант, но данные всегда свежие.
В App Router SSR включается автоматически, когда вы используете динамические функции:
// src/app/dashboard/page.tsx
// SSR — используем cookies() для проверки сессии
import { cookies } from "next/headers";
import { auth } from "@/lib/auth";
export const dynamic = "force-dynamic";
export default async function DashboardPage() {
const session = await auth();
if (!session) redirect("/login");
const projects = await prisma.project.findMany({
where: { userId: session.user.id },
orderBy: { updatedAt: "desc" },
});
return (
<div>
<h1>Мои проекты</h1>
{projects.map((p) => (
<div key={p.id}>{p.title}</div>
))}
</div>
);
}
Функции, которые делают страницу динамической (SSR):
cookies()— чтение кукheaders()— чтение заголовковsearchParams— параметры URL (?page=2&sort=date)export const dynamic = "force-dynamic"— явное указание
Страница с поиском — SSR + searchParams
// src/app/search/page.tsx
interface SearchPageProps {
searchParams: Promise<{ q?: string; page?: string }>;
}
export default async function SearchPage({ searchParams }: SearchPageProps) {
const { q = "", page = "1" } = await searchParams;
if (!q) {
return <div>Введите поисковый запрос</div>;
}
const results = await prisma.product.findMany({
where: {
OR: [
{ title: { contains: q, mode: "insensitive" } },
{ description: { contains: q, mode: "insensitive" } },
],
},
take: 20,
skip: (parseInt(page) - 1) * 20,
});
return (
<div>
<h1>Результаты: {q}</h1>
Найдено: {results.length}
{results.map((item) => (
<div key={item.id}>{item.title}</div>
))}
</div>
);
}
Эта страница — SSR, потому что использует searchParams. Каждый запрос с разными параметрами генерирует свежий HTML.
Комбинирование стратегий на одном сайте
В реальном проекте вы используете все три стратегии одновременно:
| Страница | Стратегия | Почему |
|---|---|---|
| / | ISR (60 сек) | Главная обновляется, но не критично |
| /about | SSG | Статичная, меняется раз в год |
| /blog | ISR (300 сек) | Новые статьи раз в неделю |
| /blog/[slug] | SSG + on-demand revalidation | Статьи не меняются, но можно обновить |
| /catalog | ISR (60 сек) | Товары обновляются, но не каждую секунду |
| /search | SSR | Зависит от searchParams |
| /dashboard | SSR | Персональные данные, нужна сессия |
| /cart | SSR | Зависит от cookies |
Частые ошибки
- SSR для каталога на 10 000 товаров — медленно и дорого. Используйте ISR: страница отдаётся из кеша, обновляется раз в минуту.
- SSG для страницы с ценами из внешнего API — цены устареют до следующего деплоя. ISR с
revalidate = 300решает проблему. revalidate = 0— это не «без кеша», это «ревалидация при каждом запросе». Для полного SSR используйтеdynamic = "force-dynamic".- Забыть
generateStaticParams— без него динамические SSG-страницы будут генерироваться при первом запросе (on-demand), а не при сборке. - Использование
fetchв Server Components без кеш-настроек — по умолчанию fetch в Next.js 15 не кешируется (no-store). Явно указывайте:fetch(url, { next: { revalidate: 60 } }).
Правило выбора
Простой алгоритм:
- Данные не меняются без деплоя? — SSG
- Данные меняются, но не каждую секунду? — ISR
- Данные зависят от пользователя (сессия, cookies, query)? — SSR
Если сомневаетесь — начните с ISR. Это золотая середина: быстро, свежо, масштабируемо.
Есть идея? Реализуем
Разрабатываем проекты, которые решают задачи бизнеса — от лендинга до сложного сервиса. Расскажите о своей задаче, подберём решение.

