ISR vs SSR vs SSG: когда что выбрать

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:

  1. Первый запрос после сборки — отдаётся статический HTML
  2. Через 60 секунд следующий запрос всё ещё получает кешированную версию
  3. Но в фоне Next.js перегенерирует страницу с новыми данными
  4. Следующий посетитель получает обновлённую версию

Это стратегия 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 } }).

Правило выбора

Простой алгоритм:

  1. Данные не меняются без деплоя? — SSG
  2. Данные меняются, но не каждую секунду? — ISR
  3. Данные зависят от пользователя (сессия, cookies, query)? — SSR

Если сомневаетесь — начните с ISR. Это золотая середина: быстро, свежо, масштабируемо.

Есть идея? Реализуем

Разрабатываем проекты, которые решают задачи бизнеса — от лендинга до сложного сервиса. Расскажите о своей задаче, подберём решение.

Написать в Telegram

30.03.2026

Нужна консультация?

Оставьте свои контактные данные, или свяжитесь с нами удобным для вас способом

Привет! Меня зовут Багира. Пишите, я все передам хозяевам!

Привет! Меня зовут Багира. Пишите, я все передам хозяевам!

Нажимая кнопку «Принять», вы соглашаетесь на сбор cookie. Мы используем их для обеспечения функционирования веб-сайта, аналитики действий и улучшения качества обслуживания. Если Вы не хотите, чтобы эти данные обрабатывались, отключите cookie в настройках браузера или прекратите использовать сайт.
Принять