Розробка ISR (Incremental Static Regeneration) для сайту
Типова проблема: контент-менеджери оновлюють товари, але відвідувачі бачать старі дані. ISR вирішує це — після публікації в CMS сторінка перегенерується за тегом за секунди. Користувач ніколи не чекає, а пошукові системи індексують свіжі версії. На одному з проектів з 10 000 товарів ми скоротили TTFB з 400 мс до 15 мс, а навантаження на сервер впало на 85 %. Економія на хостингу склала близько $500 на місяць.
Ми впроваджуємо Incremental Static Regeneration (ISR) — підхід, що поєднує швидкість статики зі свіжістю SSR. ISR дозволяє оновлювати окремі сторінки без повної перезбірки сайту: кешований HTML віддається за мілісекунди, а застарілі версії регенеруються у фоні. На відміну від SSR, сервер не навантажується при кожному запиті; TTFB падає в 5–10 разів. Підходить для інтернет-магазинів, блогів і порталів із контентом, що часто змінюється.
Чому розробка ISR краща за SSR для високонавантажених проектів?
Класична модель — Stale-While-Revalidate на рівні сторінок:
- Перший запит до сторінки — рендер на сервері, кешування HTML.
- Повторні запити протягом TTL — віддача з кешу, відповідь за <10ms.
- Запит після закінчення TTL — віддача застарілого кешу (користувач не чекає), запуск фонової регенерації.
- Наступний запит — свіжий HTML з оновленого кешу.
Результат: TTFB як у статики, свіжість контенту як у SSR. ISR віддає кеш за <10ms і знижує навантаження на 70–90%. ISR кращий за SSR: TTFB в 5-10 разів нижчий. Для проектів із тисячами сторінок 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. Детальніше див. у Next.js Documentation on Revalidating.
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 ефективніший за SSG для динамічного контенту, оскільки дозволяє оновлювати сторінки без повної перезбірки.
Стратегії кешування
ISR дозволяє задавати різні TTL для різних типів сторінок. Важливим аспектом є вибір стратегії інвалідації кешу (cache invalidation) та правильне налаштування edge caching на CDN.
| Тип сторінки | TTL | Логіка |
|---|---|---|
| Головна | 60 сек | Часто оновлюється |
| Категорії | 300 сек | Змінюється при додаванні товарів |
| Товари | 3600 сек | Дані стабільні, ціна — окремий запит |
| Статті блогу | 86400 сек | Рідко редагуються |
| Документація | On-demand | Тільки при публікації |
Для розподіленого деплою використовуємо зовнішнє кеш-сховище, наприклад Redis. Для високонавантажених проектів рекомендується використовувати Redis cluster для розподіленого кешування. Приклад кастомного 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 з прогрівом кешу після деплою.
- Моніторинг та оптимізація на основі метрик.
- Документація та навчання команди.
- Підтримка протягом гарантійного періоду.
Процес впровадження розробки ISR та терміни
Етапи роботи:
- Аналіз архітектури та визначення стратегії кешування (1–2 тижні).
- Налаштування ISR на обраному фреймворку (1–3 тижні).
- Інтеграція з CMS через webhook для on-demand ревалідації (1 тиждень).
- Підключення розподіленого кешу (Redis) якщо потрібно (1 тиждень).
- Налаштування CI/CD з прогрівом кешу після деплою (3–5 днів).
- Моніторинг та оптимізація на основі метрик (1–2 тижні).
Терміни: від 2 до 6 тижнів залежно від складності. Вартість розробки ISR починається від $2000 для типових проектів.
Наші інженери працюють з Next.js та Nuxt більше 7 років; ми впровадили ISR на 50+ проектах, включаючи high-load інтернет-магазини. Гарантуємо стабільну продуктивність і прозору підтримку. Отримайте консультацію з вибору стеку та стратегії кешування — зв'яжіться з нами для оцінки вашого проекту.







