التوليد الساكن التزايدي
للتوليد الساكن (static generation) الصِرف حدٌّ صارم: أي تغيير في الصفحة يستلزم إعادة البناء والنشر بالكامل. بالنسبة إلى موقع توثيق صغير، يستغرق ذلك بضع ثوانٍ ولا يلاحظه أحد. أما في سوق تضم مئة ألف صفحة منتج، فهو خط أنابيب يستغرق ساعات ويوقف إصلاح كل خطأ مطبعي.
يخفف التوليد الساكن التزايدي (Incremental Static Regeneration، ISR) هذا القيد. يظل الموقع معروضًا إلى HTML ويُقدَّم من ذاكرة CDN المؤقتة (cache)، لكن يمكن تحديث مدخلات الذاكرة بعد النشر — إما وفق مؤقت، أو استجابة مباشرة لحدث ما، مثل «جرى تحرير منتج، فأبطِل هذه الصفحة». ويمكن عرض المسارات (routes) الجديدة التي لم تكن جزءًا من البناء عند أول طلب، ثم تخزينها لكل من يأتي بعدها.
نموذج السلوك هو الأسلوب الكلاسيكي في ذاكرة HTTP، stale-while-revalidate: يحصل الطلب على HTML المخزّن فورًا، وإذا تجاوز المدخل نافذة حداثته، أعاد الإطار توليده في الخلفية من أجل الزائر التالي. لا ينتظر أحد إعادة التوليد، وتلحق الذاكرة المؤقتة بالواقع في النهاية.
نمطان ضمن ISR
يمثل ISR في الحقيقة قدرتين مترابطتين أُدمجتا معًا:
- التوليد الفوري للمسارات الجديدة — اعرض صفحة في أول مرة يطلبها أحد، ثم خزّنها مؤقتًا.
- إعادة التحقق (revalidation) للمسارات الحالية — حدّث صفحة سبق عرضها إما بعد فاصل زمني أو بعد طلب إبطال صريح.
يعرض App Router هاتين القدرتين عبر أدوات تحكم مختلفة عن تلك التي كان يقدّمه Pages Router القديم. لم يعد هناك getStaticProps({ revalidate }) أو fallback: true.
إعادة التحقق المبنية على الوقت
الحالة الأبسط: اعرض صفحة مسبقًا، ثم حدّثها كل N ثانية على الأكثر. يبقى HTML المخزّن حيًا حتى انقضاء N ثانية وطلب أحدهم الصفحة مجددًا، وعندها يعيد الإطار عرضها في الخلفية.
في App Router، تضبط ذلك إما على مستوى المسار أو لكل عملية جلب.
// app/blog/page.tsx
// Re-render at most once every 5 minutes.
export const revalidate = 300;\n
export default async function BlogIndex() {
const posts = await getAllPosts();
return <PostList posts={posts} />;
}
تمنحك إعادة التحقق لكل عملية جلب تحكمًا أدق؛ إذ يمكن لبيانات مختلفة في الصفحة نفسها أن تمتلك نوافذ حداثة مختلفة:
// app/dashboard/page.tsx
export default async function Dashboard() {
// Hourly: company-wide stats
const stats = await fetch("/api/stats", {
next: { revalidate: 3600 },
}).then((r) => r.json());\n
// Every 30 seconds: live notifications
const alerts = await fetch("/api/alerts", {
next: { revalidate: 30 },
}).then((r) => r.json());\n
return <DashboardView stats={stats} alerts={alerts} />;
}
تحدد عملية الجلب ذات نافذة إعادة التحقق الأقصر مدى تكرار إعادة عرض الصفحة نفسها. ولاحظ أن fetch في Next.js 15 لم يعد مخزّنًا مؤقتًا افتراضيًا؛ فعّل ذلك عبر next.revalidate أو cache: "force-cache".
إعادة التحقق الفورية باستخدام المسارات والوسوم
ISR على أساس الوقت مناسب لفهرس مدونة يُسمح له بأن يكون قديمًا خمس دقائق. لكنه غير صحيح للمحتوى الذي لا تُقبل فيه البيانات القديمة — فحين ينشر محرر منشورًا أو يتغير سعر، ينبغي أن تُحدَّث الصفحة المخزنة الآن، لا عند انتهاء نافذة اعتباطية.
يوفر App Router لك أداتين دقيقتين: revalidatePath وrevalidateTag.
revalidatePath يبطّل عنوان URL محددًا.
// app/api/revalidate/route.ts
import { revalidatePath } from "next/cache";
import { NextResponse } from "next/server";\n
export async function POST(req: Request) {
const { secret, slug } = await req.json();
if (secret !== process.env.REVALIDATE_SECRET) {
return NextResponse.json({ ok: false }, { status: 401 });
}\n
revalidatePath(`/blog/${slug}`);
return NextResponse.json({ revalidated: true });
}
اربط CMS ليستدعي نقطة النهاية هذه عند النشر. خلال ثانية أو ثانيتين، سيحصل الطلب التالي إلى /blog/ على النسخة الحديثة.
revalidateTag يبطّل كل عملية جلب وُسمت بالسلسلة المعطاة، مهما كان المسار الذي تقع فيه.
// app/products/[id]/page.tsx
export default async function Product({ params }) {
const { id } = await params;
const product = await fetch(`https://api/products/${id}`, {
next: { tags: [`product:${id}`, "products"] },
}).then((r) => r.json());
return <ProductView product={product} />;
}
عندما يتغير المنتج، تستدعي revalidateTag("product:" + id) فتتحدث صفحات ذلك المنتج وحدها. وعندما تغيّر الأسعار في الكتالوج كله، يبطّل revalidateTag("products") كل صفحة استخدمت جلب منتج. وهذه أنظف طريقة لنمذجة إبطال الذاكرة المؤقتة في موقع يدعم CMS؛ إذ تتوافق وسومك مع نموذج التحرير، لا مع بنية عناوين URL.
يمكنك أيضًا استدعاء revalidateTag وrevalidatePath مباشرة من Server Action، وهي الطريقة غالبًا أنظف من إنشاء نقطة نهاية منفصلة لإعادة التحقق:
"use server";
import { revalidateTag } from "next/cache";\n
export async function publishPost(formData: FormData) {
const post = await db.posts.create({
title: formData.get("title"),
body: formData.get("body"),
});
revalidateTag("posts");
return post;
}
توليد الصفحات عند الطلب
بالنسبة إلى المواقع التي تملك عددًا كبيرًا جدًا من المسارات الممكنة يتعذر عرضها جميعًا مسبقًا — مثل سوق يضم ملايين وحدات المخزون أو CMS بأسماء غير محدودة — فاعرض المسارات الشائعة مسبقًا ودع الباقي يُعرض عند أول طلب.
// app/products/[id]/page.tsx\n
// Prerender the top 1,000 most-viewed products at build time.
export async function generateStaticParams() {
const top = await getTopProducts(1000);
return top.map((p) => ({ id: p.id }));
}\n
// Allow other ids to render on demand and be cached afterward.
export const dynamicParams = true;\n
// Re-render any cached entry at most once an hour.
export const revalidate = 3600;\n
export default async function Product({ params }) {
const { id } = await params;
const product = await getProduct(id);
if (!product) notFound();
return <ProductView product={product} />;
}
يؤدي أول طلب لمنتج غير مخزّن إلى تشغيل العرض على الخادم، ثم تدخل النتيجة ذاكرة CDN المؤقتة لتخدم الجميع. يبقى البناء سريعًا، إذ يضم 1,000 صفحة لا 1,000,000 صفحة، ويظل الذيل الطويل سريع التحميل بعد إحمائه الأول، وتتحدث المدخلات القديمة تلقائيًا في الخلفية.
إذا أردت للمعاملات غير المعروفة أن تُعيد 404 بدل أن تُولَّد عند أول استخدام، فاضبط dynamicParams = false.
أين يعمل ISR
يكون ISR أقوى ما يكون عند تشغيله على الحافة (edge). تنفذ Vercel وNetlify وCloudflare عمليات إعادة التوليد الفورية من بيئة تشغيل الحافة وتحفظ النتيجة في ذاكرة مؤقتة عالمية. يرى الزوار في كل قارة ردودًا ساكنة سريعة من دون أن يضطر أحد إلى رحلة ذهاب وعودة إلى أصل مركزي.
هناك نقطة دقيقة لكنها مهمة: إعادة التحقق لا تنتشر فورًا إلى كل نقطة حضور (POP) على الحافة. عندما تستدعي revalidatePath، تُوسم مدخلة الذاكرة المؤقتة قديمة. ويؤدي الطلب التالي إلى نقطة حضور بعينها إلى إعادة توليدها هناك. تنتشر معظم المنصات إشارة الإبطال بسرعة، لكن إذا احتاج محرروك إلى اتساق عالمي دقيق بمجرد الضغط على نشر، فخطط أيضًا لمسح ذاكرة CDN المؤقتة مباشرة.
متى تكون ISR الأداة المناسبة
| نوع الصفحة | مناسبة؟ |
|---|---|
| صفحات تسويق تُحدَّث أسبوعيًا | نعم — revalidate: 86400. |
| مقالات أخبار ومنشورات مدونات | نعم — إعادة التحقق بالوسوم عند النشر. |
| صفحات فئات التجارة الإلكترونية | نعم — بالوسوم، مع إعادة التحقق عند تغير المخزون. |
| صفحات تفاصيل المنتج | نعم — اعرض المنتجات الأكثر مبيعًا مسبقًا، والذيل الطويل عند الطلب. |
| النتائج الرياضية المباشرة | لا — استخدم SSR أو SSR بالبث. |
| لوحة معلومات مخصصة | لا — استخدم SSR أو PPR مع فتحات ديناميكية. |
| نتائج البحث | لا — عدد التوليفات كبير جدًا؛ استخدم SSR. |
المقايضات
إبطال الذاكرة المؤقتة مشكلة حقيقية — وهي المسألة الصعبة الثانية في علوم الحاسوب. يضع ISR الصعوبة بين يديك: عليك تذكّر الوسوم أو المسارات التي يجب إبطالها عند تغير البيانات، ونسيان أي منها يعني أن يرى المستخدمون أسعارًا أو مخزونًا أو عناوين قديمة. تجعل إعادة التحقق بالوسوم هذه المهمة قابلة للإدارة، لكن عاملها كأداة انضباط لا كميزة.
توليد سيئ يبقى في المكان. إذا أعاد مصدر البيانات استجابة جزئية أو فشل عرض ما، فقد تُخزَّن النسخة السيئة وتُقدَّم للزائر التالي. ضع جلب البيانات داخل حد أخطاء حقيقي وأعد خطأً عند الفشل للحفاظ على آخر HTML جيد في الذاكرة المؤقتة؛ إذ لن يستبدل Next.js مدخلة ذاكرة صالحة بمدخلة أخرى في حالة الخطأ.
قابلية مراقبة ISR مهارة قائمة بذاتها. نسب الإصابة/الخطأ لكل مسار، ومتوسط زمن إعادة التوليد، وعدد عمليات إعادة التحقق في الخلفية — لا شيء من ذلك موجود في حزمة المراقبة الافتراضية. تعرض معظم المنصات مقاييس له؛ فتعرّف على مقاييس منصتك.
المحتوى الذي يتطلب مصادقة لا ينتمي هنا. الفكرة الأساسية لـ ISR هي أن HTML واحدًا معروضًا يخدم مستخدمين كثيرين. وإذا كان HTML يجب أن يختلف لكل مستخدم، فاستخدم SSR، أو هيكلًا ساكنًا مع منطقة مخصصة يجلبها العميل.
ISR مقابل SSR مقابل SSG، نظرة سريعة
| الخاصية | SSG | ISR | SSR |
|---|---|---|---|
| زمن العرض | البناء | البناء + عند الطلب + في الخلفية | لكل طلب |
| الحداثة | حتى النشر التالي | حتى إشارة إعادة التحقق التالية | محدّثة دائمًا |
| TTFB | ذاكرة الحافة (ممتاز) | ذاكرة الحافة (ممتاز)، مع إعادة توليد أحيانًا | الأصل أو الحافة (جيد) |
| التخصيص | لا | لا | نعم |
| تكلفة التشغيل | الأدنى | منخفضة | الأعلى |
| وضع الفشل | يلزم إعادة البناء بالكامل لإصلاحه | إعادة تحقق سيئة؛ يقدَّم آخر إصدار جيد | يفشل الطلب برمز 500 |
تكون ISR هي الحل الصحيح عندما تستحق معظم حركاتك اقتصاديات SSG، لكن المحتوى يحتاج إلى الاستمرار في التغير. يعالج النمط التالي — SSR بالبث — مشكلة مختلفة: الصفحات التي تحتاج فعلًا إلى أن تكون ديناميكية، لكن يجب ألا تجعل المستخدم ينتظر حتى تصبح كل بايت جاهزة قبل بدء الرسم.