الفرق بين SSR، ISR، CSR و SSG

10 جويلية 2025 • 15 دقيقة قراءة

Article Cover

في عالم تطوير الويب الحديث، لم يعد إنشاء موقع إلكتروني يتمحور فقط حول "متى يظهر المحتوى"، بل أصبح السؤال الأهم هو: كيف وأين ومتى يتم توليد الصفحة التي يراها المستخدم؟ تقنيات مثل SSG وSSR وCSR وISR ظهرت لتلبية احتياجات مختلفة تتعلق بسرعة تحميل الصفحة، تفاعلها، حداثة بياناتها، وتجربة المستخدم العامة.

تخيل معي هذا المشهد

  • تدخل إلى مطعم، جائع ومتحمس لتجربة وجبة مميزة.
  • النادل يقدم لك قائمة طعام واحدة فقط... لكنها تُكتب أمامك على الفور حسب رغبتك!
  • بينما في مطعم آخر، كل الأطباق جاهزة مسبقًا، لكنها قد لا تكون طازجة بالكامل.
  • وفي مكان ثالث، يُعطيك النادل مكونات خام ويقول: "اطبخها بنفسك!"

كيف تختار؟ الأمر يعتمد على سرعة تقديم الطبق، طزاجته، وتجربتك كزبون.

في عالم تطوير الويب، تحدث المعضلة نفسها لكن مع صفحات الويب بدلًا من الأطباق!

  • بعض المواقع تُحضَّر بالكامل قبل زيارتك...
  • وأخرى تُطبخ خصيصًا لك عند الزيارة...
  • وثالثة تترك كل العمل لمتصفحك!

السؤال المحوري:

كيف تختار الطريقة المثلى لـ"طبخ" موقعك دون أن يمل الزائر أو يهرب؟

في هذه المقالة، سنستخدم تشبيه "المطاعم" لفهم 4 استراتيجيات مختلفة لبناء المواقع الحديثة، بحيث تخرج بإجابة واضحة:

متى تستخدم SSG، SSR، CSR، أو ISR؟

1 - توليد الصفحات الثابتة SSG - Static Site Generation

المطعم الذي يُحضر كل شيء مسبقًا

SSG

التشبيه الموسع

مطعم يُحضّر الوجبات مسبقًا قبل فتح أبوابه، ويضعها في واجهة العرض.

  1. يقوم الخادم (الطاهي) بتحضير جميع الصفحات مسبقًا أثناء عملية البناء قبل وصول المستخدمين.
  2. يزور المستخدم الموقع ويطلب صفحة معينة.
  3. يحصل مباشرة على نسخة جاهزة من شبكة CDN، تمامًا مثل وجبة جاهزة على الرف دون أي انتظار.

مثال حقيقي: مدونات Hugo أو Jekyll، مواقع الشركات الثابتة (مثل صفحة "من نحن").

التفاصيل التقنية

  • يتم بناء كل الصفحات أثناء عملية "Build" (مثل npm run build في Next.js).
  • الناتج: مجلد out/ أو dist/ يحتوي على HTML جاهز.
  • يُخزَّن على شبكة CDN ليكون أقرب للمستخدمين جغرافيا.

المزايا

  • سريع جدًا، لأن الوجبة جاهزة.
  • مثالي للمحتوى الثابت (مثل مدونة أو صفحة تعريفية).

العيوب

  • إذا تغيّر محتوى الوجبة، يجب على الطاهي إعادة تجهيز كل شيء (إعادة بناء الموقع).

متى تُستخدم؟

  • المحتوى الذي لا يتغير كثيرًا (مقالات، توثيقات، مواقع شخصية).
  • المشاريع التي تُبنى مرة واحدة وتُنشر مرارًا (مثل موقع لشركة صغيرة).

التحديات

إذا كان لديك 10,000 صفحة، إعادة البناء الكامل قد تستغرق دقائق أو ربما ساعات!

الحلول

  • التجزئة (Incremental Builds): تحديث الصفحات المتغيرة فقط (مثل Gatsby).
  • استخدام ISR (سنشرحها لاحقًا).

2 - توليد الصفحات عند الخادم SSR - Server-Side Rendering

المطعم الذي يطبخ عند الطلب

SSR

التشبيه الموسع

مطعم يقدم أطباقًا حسب الطلب، مثل السوشي أو الباستا. كل زبون ينتظر دقائق حتى تُحضَّر وجبته الطازجة.

  1. يزور المستخدم الموقع ويطلب صفحة معينة.
  2. يقوم الخادم (الطاهي) بتحضير الصفحة في اللحظة نفسها بناءً على الطلب.
  3. بعد الانتهاء، يُرسل الخادم الصفحة الجاهزة إلى المتصفح ليتم عرضها للمستخدم.

مثال حقيقي: مواقع الأخبار (CNN)، المتاجر ذات المخزون الحيوي (مثل Amazon صفحة المنتج).

التفاصيل التقنية

  • الخادم يُنشئ HTML لكل طلب مستخدم (مثلاً باستخدام getServerSideProps في النسخ السابقة من Next.js).
  • يُرسَل HTML مكتمل إلى المتصفح، مما يفيد SEO (محركات البحث ترى المحتوى كاملاً).

متى تُستخدم؟

  • البيانات الديناميكية جدًا (مثل أسعار الأسهم، الطقس).
  • الصفحات التي تحتاج إلى تحديث فوري (مثل لوحة تحكم المستخدم).

التحديات

ضغط على الخادم: كل زائر = طلب جديد.

الحلول

  • التخزين المؤقت (Caching): تخزين النتائج لوقت قصير (مثلاً 5 دقائق).
  • الدمج مع SSG: استخدام SSR فقط للصفحات الديناميكية.

3 - توليد الصفحا ت عند العميل CSR - Client-Side Rendering

المطعم الذي يعطي الزبون مكونات ليطبخها بنفسه!

CSR

مثل وجبات "HelloFresh" التي تُرسل إليك مكونات لتحضيرها في المنزل. المتصفح (المطبخ) يقوم بكل العمل!

  1. يزور المستخدم الموقع ويطلب صفحة معينة.
  2. يُرسل الخادم ملف HTML بسيط ومعه ملفات JavaScript، أي "المكونات الخام وتعليمات التحضير".
  3. يقوم المتصفح (مطبخ المستخدم) بتجميع البيانات وتحضير الصفحة بالكامل محليًا.

مثال حقيقي: تطبيقات الويب التفاعلية (مثل Gmail، Trello).

التفاصيل التقنية

متى تُستخدم؟

  • التطبيقات التي تحتاج تفاعلًا عاليًا (محرر نصوص، لوحات تحكم).
  • عندما لا يكون SEO أولوية (تطبيقات الدخول مثل Slack).

التحديات

بطء التحميل الأولي (خاصة على أجهزة mobile).

الحلول

  • التحميل المسبق (Preloading): جلب البيانات أثناء تحميل JS.
  • التقسيم الكودي (Code Splitting): تحميل الأجزاء المهمة أولاً.

4 - التجديد الثابت التدريجي ISR - Incremental Static Regeneration

المطعم الذي يُحدّث الأطباق تلقائيًا

ISR

التشبيه الموسع

مطعم يُعدّ الأطباق مسبقًا، لكنه يفحص كل ساعة أكثر الأطباق طلبًا ويُجدّدها في الخلفية.

  1. يقوم الخادم (الطاهي) بتحضير الصفحات مسبقًا أثناء البناء، تمامًا مثل SSG.
  2. يزور المستخدم الموقع ويطلب صفحة معينة.
  3. إذا كانت الصفحة جاهزة في التخزين المؤقت (CDN)، تُقدَّم فورًا للمستخدم.
  4. إذا لم تكن الصفحة موجودة بعد، يُحضّرها الخادم ويرسلها للمستخدم، ويُخزّنها للزيارات القادمة.
  5. تُحدث النسخة المخزنة تلقائيًا في الخلفية بعد مدة محددة (مثل كل ساعة)، أو يدويًا عند الحاجة.

مثال حقيقي: متاجر إلكترونية (مثل 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