Разработка ISR (Incremental Static Regeneration) для сайта
Мы внедряем Incremental Static Regeneration (ISR) — подход, сочетающий скорость статики со свежестью SSR. ISR позволяет обновлять отдельные страницы без полной пересборки сайта: кэшированный HTML отдаётся за миллисекунды, а устаревшие версии регенерируются в фоне. В отличие от SSR, сервер не нагружается при каждом запросе; TTFB падает в 5–10 раз. Подходит для интернет-магазинов, блогов и порталов с часто меняющимся контентом.
Типичная проблема: контент-менеджеры обновляют товары, но посетители видят старые данные. ISR решает это — после публикации в CMS страница перегенерируется по тегу за секунды. Пользователь никогда не ждёт, а поисковые системы индексируют свежие версии. На одном из проектов с 10 000 товаров мы сократили TTFB с 400 мс до 15 мс, а нагрузка на сервер упала на 85 %.
Почему ISR лучше SSR для высоконагруженных проектов?
Классическая модель — Stale-While-Revalidate на уровне страниц:
- Первый запрос к странице — рендер на сервере, кэширование HTML.
- Повторные запросы в течение TTL — отдача из кэша, ответ за <10ms.
- Запрос после истечения TTL — отдача устаревшего кэша (пользователь не ждёт), запуск фоновой регенерации.
- Следующий запрос — свежий HTML из обновлённого кэша.
Результат: TTFB как у статики, свежесть контента как у SSR. ISR отдаёт кэш за <10ms и снижает нагрузку на 70–90%. При этом контент остаётся свежим в пределах TTL. Для проектов с тысячами страниц ISR практически не требует серверных ресурсов во время пиков.
Как настроить on-demand revalidation?
TTL-кэш не подходит, когда нужно обновить страницу сразу после изменения в CMS. Для этого используется on-demand revalidation через API:
// app/api/revalidate/route.ts
import { revalidateTag, revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const secret = request.headers.get('x-revalidate-secret');
if (secret !== process.env.REVALIDATE_SECRET) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { tag, path } = await request.json();
if (tag) revalidateTag(tag);
if (path) revalidatePath(path);
return NextResponse.json({ revalidated: true });
}Webhook из CMS вызывает этот endpoint при публикации. Мы интегрируем с Contentful, Strapi, WordPress и другими системами.
Реализация в Next.js App Router и Nuxt 3
Next.js
// app/products/[id]/page.tsx
interface Props {
params: { id: string };
}
async function getProduct(id: string) {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { revalidate: 300, tags: [`product-${id}`] },
});
if (!res.ok) return null;
return res.json();
}
export default async function ProductPage({ params }: Props) {
const product = await getProduct(params.id);
if (!product) notFound();
return <ProductView product={product} />;
}
export async function generateStaticParams() {
const popularProducts = await fetch('https://api.example.com/products?popular=true&limit=100')
.then(r => r.json());
return popularProducts.map(({ id }) => ({ id }));
}Страницы из generateStaticParams генерируются при сборке. Остальные — при первом запросе и затем регенерируются по TTL.
Nuxt 3 — страница использует useFetch с ключом, а серверный обработчик оборачивается в cachedEventHandler:
// server/api/products/[id].ts
export default cachedEventHandler(
async (event) => {
const id = getRouterParam(event, 'id');
return await $fetch(`https://api.example.com/products/${id}`);
},
{
maxAge: 300,
staleMaxAge: 3600,
name: 'product',
getKey: (event) => `product-${event.context.params.id}`,
}
);
Сравнение SSG, SSR и ISR
| Параметр | SSG | SSR | ISR |
|---|---|---|---|
| TTFB | <10ms | 200–500ms | <10ms |
| Свежесть контента | Только при сборке | Всегда свежий | В пределах TTL |
| Нагрузка на сервер | Минимальная | Высокая | Низкая |
| Регенерация | Полная сборка | Нет | Фоновая, постранично |
Стратегии кэширования
ISR позволяет задавать разные TTL для разных типов страниц:
| Тип страницы | TTL | Логика |
|---|---|---|
| Главная | 60 сек | Часто обновляется |
| Категории | 300 сек | Меняется при добавлении товаров |
| Товары | 3600 сек | Данные стабильные, цена — отдельный запрос |
| Статьи блога | 86400 сек | Редко редактируются |
| Документация | On-demand | Только при публикации |
Для распределённого деплоя используем внешнее кэш-хранилище, например Redis. Пример кастомного cache-handler для Next.js
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
cacheHandler: process.env.NODE_ENV === 'production' ? require.resolve('./cache-handler.js') : undefined,
cacheMaxMemorySize: 0,
};
// cache-handler.js
const redis = require('ioredis');
const client = new redis(process.env.REDIS_URL);
module.exports = class CacheHandler {
async get(key) {
const data = await client.get(key);
return data ? JSON.parse(data) : null;
}
async set(key, data, ctx) {
const ttl = ctx.revalidate || 3600;
await client.setex(key, ttl, JSON.stringify({ value: data, lastModified: Date.now() }));
}
async revalidateTag(tag) {
const keys = await client.smembers(`tag:${tag}`);
if (keys.length) await client.del(...keys);
await client.del(`tag:${tag}`);
}
}; Мониторинг и отладка
Отслеживайте в production: cache hit rate, revalidation duration, stale responses. Мы настраиваем метрики в Grafana. Пример: добавление заголовка x-cache-time через middleware помогает анализировать актуальность кэша.
Что входит в работу
- Аудит текущей архитектуры и производительности.
- Проектирование стратегии кэширования с подбором TTL.
- Реализация ISR на выбранном фреймворке (Next.js, Nuxt 3 или кастомное решение).
- Интеграция on-demand revalidation через webhook CMS.
- Подключение распределённого кэша (Redis) при необходимости.
- Настройка CI/CD с прогревом кэша после деплоя.
- Мониторинг и оптимизация на основе метрик.
- Документация и обучение команды.
- Поддержка в течение гарантийного периода.
Процесс внедрения и сроки
Этапы работы:
- Анализ архитектуры и определение стратегии кэширования (1–2 недели).
- Настройка ISR на выбранном фреймворке (1–3 недели).
- Интеграция с CMS через webhook для on-demand ревалидации (1 неделя).
- Подключение распределённого кэша (Redis) если требуется (1 неделя).
- Настройка CI/CD с прогревом кэша после деплоя (3–5 дней).
- Мониторинг и оптимизация на основе метрик (1–2 недели).
Сроки: от 2 до 6 недель в зависимости от сложности. Стоимость рассчитывается индивидуально.
Наши инженеры работают с Next.js и Nuxt более 7 лет; мы внедрили ISR на 50+ проектах, включая high-load интернет-магазины. Гарантируем стабильную производительность и прозрачную поддержку. Получите консультацию по выбору стека и стратегии кэширования — свяжитесь с нами для оценки вашего проекта.







