تخيل معي هذا المشهد
- تدخل إلى مطعم، جائع ومتحمس لتجربة وجبة مميزة.
- النادل يقدم لك قائمة طعام واحدة فقط... لكنها تُكتب أمامك على الفور حسب رغبتك!
- بينما في مطعم آخر، كل الأطباق جاهزة مسبقًا، لكنها قد لا تكون طازجة بالكامل.
- وفي مكان ثالث، يُعطيك النادل مكونات خام ويقول: "اطبخها بنفسك!"
كيف تختار؟ الأمر يعتمد على سرعة تقديم الطبق، طزاجته، وتجربتك كزبون.
في عالم تطوير الويب، تحدث المعضلة نفسها لكن مع صفحات الويب بدلًا من الأطباق!
- بعض المواقع تُحضَّر بالكامل قبل زيارتك...
- وأخرى تُطبخ خصيصًا لك عند الزيارة...
- وثالثة تترك كل العمل لمتصفحك!
السؤال المحوري:
كيف تختار الطريقة المثلى لـ"طبخ" موقعك دون أن يمل الزائر أو يهرب؟
في هذه المقالة، سنستخدم تشبيه "المطاعم" لفهم 4 استراتيجيات مختلفة لبناء المواقع الحديثة، بحيث تخرج بإجابة واضحة:
متى تستخدم SSG، SSR، CSR، أو ISR؟
1 - توليد الصفحات الثابتة SSG - Static Site Generation
المطعم الذي يُحضر كل شيء مسبقًا

التشبيه الموسع
مطعم يُحضّر الوجبات مسبقًا قبل فتح أبوابه، ويضعها في واجهة العرض.
- يقوم الخادم (الطاهي) بتحضير جميع الصفحات مسبقًا أثناء عملية البناء قبل وصول المستخدمين.
- يزور المستخدم الموقع ويطلب صفحة معينة.
- يحصل مباشرة على نسخة جاهزة من شبكة CDN، تمامًا مثل وجبة جاهزة على الرف دون أي انتظار.
مثال حقيقي: مدونات Hugo أو Jekyll، مواقع الشركات الثابتة (مثل صفحة "من نحن").
التفاصيل التقنية
- يتم بناء كل الصفحات أثناء عملية "Build" (مثل
npm run build
في Next.js). - الناتج: مجلد
out/
أوdist/
يحتوي على HTML جاهز. - يُخزَّن على شبكة CDN ليكون أقرب للمستخدمين جغرافيا.
المزايا
- سريع جدًا، لأن الوجبة جاهزة.
- مثالي للمحتوى الثابت (مثل مدونة أو صفحة تعريفية).
العيوب
- إذا تغيّر محتوى الوجبة، يجب على الطاهي إعادة تجهيز كل شيء (إعادة بناء الموقع).
متى تُستخدم؟
- المحتوى الذي لا يتغير كثيرًا (مقالات، توثيقات، مواقع شخصية).
- المشاريع التي تُبنى مرة واحدة وتُنشر مرارًا (مثل موقع لشركة صغيرة).
التحديات
إذا كان لديك 10,000 صفحة، إعادة البناء الكامل قد تستغرق دقائق أو ربما ساعات!
الحلول
- التجزئة (Incremental Builds): تحديث الصفحات المتغيرة فقط (مثل Gatsby).
- استخدام ISR (سنشرحها لاحقًا).
2 - توليد الصفحات عند الخادم SSR - Server-Side Rendering
المطعم الذي يطبخ عند الطلب

التشبيه الموسع
مطعم يقدم أطباقًا حسب الطلب، مثل السوشي أو الباستا. كل زبون ينتظر دقائق حتى تُحضَّر وجبته الطازجة.
- يزور المستخدم الموقع ويطلب صفحة معينة.
- يقوم الخادم (الطاهي) بتحضير الصفحة في اللحظة نفسها بناءً على الطلب.
- بعد الانتهاء، يُرسل الخادم الصفحة الجاهزة إلى المتصفح ليتم عرضها للمستخدم.
مثال حقيقي: مواقع الأخبار (CNN)، المتاجر ذات المخزون الحيوي (مثل Amazon صفحة المنتج).
التفاصيل التقنية
- الخادم يُنشئ HTML لكل طلب مستخدم (مثلاً باستخدام
getServerSideProps
في النسخ السابقة من Next.js). - يُرسَل HTML مكتمل إلى المتصفح، مما يفيد SEO (محركات البحث ترى المحتوى كاملاً).
متى تُستخدم؟
- البيانات الديناميكية جدًا (مثل أسعار الأسهم، الطقس).
- الصفحات التي تحتاج إلى تحديث فوري (مثل لوحة تحكم المستخدم).
التحديات
ضغط على الخادم: كل زائر = طلب جديد.
الحلول
- التخزين المؤقت (Caching): تخزين النتائج لوقت قصير (مثلاً 5 دقائق).
- الدمج مع SSG: استخدام SSR فقط للصفحات الديناميكية.
3 - توليد الصفحا ت عند العميل CSR - Client-Side Rendering
المطعم الذي يعطي الزبون مكونات ليطبخها بنفسه!

مثل وجبات "HelloFresh" التي تُرسل إليك مكونات لتحضيرها في المنزل. المتصفح (المطبخ) يقوم بكل العمل!
- يزور المستخدم الموقع ويطلب صفحة معينة.
- يُرسل الخادم ملف HTML بسيط ومعه ملفات JavaScript، أي "المكونات الخام وتعليمات التحضير".
- يقوم المتصفح (مطبخ المستخدم) بتجميع البيانات وتحضير الصفحة بالكامل محليًا.
مثال حقيقي: تطبيقات الويب التفاعلية (مثل Gmail، Trello).
التفاصيل التقنية
متى تُستخدم؟
- التطبيقات التي تحتاج تفاعلًا عاليًا (محرر نصوص، لوحات تحكم).
- عندما لا يكون SEO أولوية (تطبيقات الدخول مثل Slack).
التحديات
بطء التحميل الأولي (خاصة على أجهزة mobile).
الحلول
- التحميل المسبق (Preloading): جلب البيانات أثناء تحميل JS.
- التقسيم الكودي (Code Splitting): تحميل الأجزاء المهمة أولاً.
4 - التجديد الثابت التدريجي ISR - Incremental Static Regeneration
المطعم الذي يُحدّث الأطباق تلقائيًا

التشبيه الموسع
مطعم يُعدّ الأطباق مسبقًا، لكنه يفحص كل ساعة أكثر الأطباق طلبًا ويُجدّدها في الخلفية.
- يقوم الخادم (الطاهي) بتحضير الصفحات مسبقًا أثناء البناء، تمامًا مثل SSG.
- يزور المستخدم الموقع ويطلب صفحة معينة.
- إذا كانت الصفحة جاهزة في التخزين المؤقت (CDN)، تُقدَّم فورًا للمستخدم.
- إذا لم تكن الصفحة موجودة بعد، يُحضّرها الخادم ويرسلها للمستخدم، ويُخزّنها للزيارات القادمة.
- تُحدث النسخة المخزنة تلقائيًا في الخلفية بعد مدة محددة (مثل كل ساعة)، أو يدويًا عند الحاجة.
مثال حقيقي: متاجر إلكترونية (مثل Shopify)، المدونات الكبيرة (مثل Dev.to)، مدونتي الشخصية.
التفاصيل التقنية
- في Next.js، تُحدد
revalidate: 60
مثلا لتحديث الصفحة كل 60 ثانية. - أول زائر يحصل على النسخة القديمة، بينما الخادم يُعدّ نسخة جديدة.
- الزائر التالي يحصل على المحدث!
متى تُستخدم؟
- المحتوى شبه الديناميكي (مقالات تُحدث يوميًا، منتجات متغيرة).
- المواقع الكبيرة التي لا يمكن إعادة بنائها بالكامل كل مرة.
التحديات
قد يرى المستخدم بيانات قديمة لثوانٍ.
الحلول
استخدام On-Demand ISR (تحديث الصفحة يدويًا عند حدث معين، مثل تغيير سعر منتج).
5-شرح مثال تطبيق ISR يدويًا
في هذا المثال، نحاول محاكاة سلوك ISR (Incremental Static Regeneration) يدويًا باستخدام:
- Hono: إطار عمل صغير وخفيف لبناء التطبيقات فوق Cloudflare Workers.
- KV Storage: قاعدة بيانات تعتمد على المفاتيح والقيم (Key-Value) لتخزين الصفحات مؤقتًا.
الفكرة الأساسية
نحن نريد أن:
- نخزن نسخة HTML من الصفحة في التخزين المؤقت.
- نخدم هذه النسخة بسرعة عند كل زيارة (مثل SSG).
- نحدثها كل ساعة (مثل ISR).
import { Hono } from "hono"
interface Env {
KV: KVNamespace
}
const app = new Hono<{ Bindings: Env }>()
app.get("/", async (c) => {
// توليد رقم عشوائي بين 1 و 1000
const rand = Math.floor(Math.random() * 1000) + 1
// محتوى ديناميكي
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
<h1>${rand}</h1>
</body>
</html>
`
// محاولة جلب النسخة المخزنة
const cached = await c.env.KV.get("home")
if (cached) {
// إذا وجدت نسخة مخزنة، نرسلها فورًا
return c.html(cached)
}
// اذا لم نجد النسخة المخزنة، نخزن نسخة جديدة
await c.env.KV.put("home", html, {
expirationTtl: 60 * 60, // تخزين لساعة واحدة
})
// نعيد المحتوى الديناميكي
return c.html(html)
})
app.get("/api/revalidate", async (c) => {
// إعادة التحقق من الصفحة الرئيسية
const cached = await c.env.KV.get("home")
if (cached) {
// إذا وجدت نسخة مخزنة، نقوم بحذفها
await c.env.KV.delete("home")
}
// نعيد رسالة نجاح
return c.json({ message: "Revalidation successful" })
})
export default app