ISR (רגנרציה סטטית מצטברת) לאתר שלך
אנו מיישמים רגנרציה סטטית מצטברת (ISR) — גישה המשלבת את המהירות של אתרים סטטיים עם הרעננות של SSR. ISR מעדכן דפים בודדים ללא בנייה מחדש מלאה: HTML שמור במטמון מוגש תוך אלפיות שניות, וגרסאות מיושנות מתחדשות ברקע. בניגוד ל-SSR, השרת אינו נטען בכל בקשה; TTFB יורד פי 5–10. אידיאלי לחנויות מסחר אלקטרוני, בלוגים ופורטלים עם תוכן משתנה תדיר.
בעיה טיפוסית: מנהלי תוכן מעדכנים מוצרים, אך המבקרים רואים נתונים ישנים. ISR פותר זאת — לאחר פרסום ב-CMS, הדף מתחדש לפי תג תוך שניות. המשתמשים לעולם לא מחכים, ומנועי החיפוש מאנדקסים גרסאות חדשות. בפרויקט אחד עם 10,000 מוצרים, קיצרנו את ה-TTFB מ-400ms ל-15ms ועומס השרת ירד ב-85%.
למה ISR עדיף על SSR לפרויקטים בעלי עומס גבוה
המודל הקלאסי הוא Stale-While-Revalidate ברמת הדף:
- בקשה ראשונה לדף — רינדור בצד השרת, שמירת ה-HTML במטמון.
- בקשות עוקבות בתוך TTL — מוגשות מהמטמון, תגובה <10ms.
- בקשה לאחר פקיעת TTL — הגשת מטמון מיושן (המשתמש לא מחכה), הפעלת רגנרציה ברקע.
- בקשה הבאה — HTML חדש מהמטמון המעודכן.
תוצאה: TTFB נמוך כמו סטטי, רעננות תוכן כמו SSR. ISR מגיש מטמון ב-<10ms ומפחית את עומס השרת ב-70–90%. בינתיים, התוכן נשאר רענן בתוך TTL. לפרויקטים עם אלפי דפים, ISR צורך כמעט ללא משאבי שרת בזמני שיא.
איך להגדיר רגנרציה לפי דרישה
מטמון מבוסס TTL אינו מספיק כשצריך לרענן דף מיד לאחר שינוי ב-CMS. לשם כך, השתמשו ברגנרציה לפי דרישה דרך 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 }));
}דפים מ-// 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 }); } נוצרים בזמן בנייה. אחרים נוצרים בבקשה הראשונה ולאחר מכן מתחדשים לפי TTL.
Nuxt 3 — דף משתמש ב-// 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 })); } עם מפתח, ו-handler השרת עטוף ב-generateStaticParams:
// 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 | לוגיקה |
|---|---|---|
| דף הבית | 60s | מתעדכן תדיר |
| קטגוריות | 300s | משתנה כשמוסיפים מוצרים |
| מוצרים | 3600s | נתונים יציבים; מחיר דרך בקשה נפרדת |
| מאמרי בלוג | 86400s | נערכים לעיתים רחוקות |
| תיעוד | לפי דרישה | רק בעת פרסום |
לפריסות מבוזרות, אנו משתמשים במאגר מטמון חיצוני כמו 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}`);
}
}; ניטור וניפוי שגיאות
עקוב בייצור: שיעור פגיעות מטמון, משך רגנרציה, תגובות מיושנות. אנו מגדירים מדדים ב-Grafana. לדוגמה, הוספת כותרת useFetch דרך middleware עוזרת לנתח רעננות מטמון.
מה כלול בעבודה
- ביקורת על הארכיטקטורה והביצועים הנוכחיים.
- עיצוב אסטרטגיית מטמון עם בחירת TTL.
- יישום ISR על הפריימוורק הנבחר (Next.js, Nuxt 3, או פתרון מותאם).
- שילוב רגנרציה לפי דרישה דרך webhook של CMS.
- הגדרת מטמון מבוזר (Redis) אם נדרש.
- הגדרת CI/CD עם חימום מטמון לאחר פריסה.
- ניטור ואופטימיזציה לפי מדדים.
- תיעוד והדרכת צוות.
- תמיכה בתקופת אחריות.
תהליך היישום ולוחות זמנים
שלבים:
- ניתוח ארכיטקטורה והגדרת אסטרטגיית מטמון (1–2 שבועות).
- הגדרת ISR על הפריימוורק הנבחר (1–3 שבועות).
- שילוב עם CMS דרך webhook לרגנרציה לפי דרישה (שבוע).
- הגדרת מטמון מבוזר (Redis) אם נדרש (שבוע).
- הגדרת CI/CD עם חימום מטמון (3–5 ימים).
- ניטור ואופטימיזציה לפי מדדים (1–2 שבועות).
לוחות זמנים: 2 עד 6 שבועות תלוי במורכבות. התמחור מבוסס פרויקט.
למהנדסים שלנו ניסיון של למעלה מ-7 שנים עם Next.js ו-Nuxt; יישמנו ISR על יותר מ-50 פרויקטים, כולל חנויות מסחר אלקטרוני בעלות עומס גבוה. אנו מבטיחים ביצועים יציבים ותמיכה שקופה. קבלו ייעוץ על בחירת מחסנית ואסטרטגיית מטמון — צרו קשר להערכת פרויקט.







