Next.js
Server Components في Next.js — دليل شامل 2026
📅 2026-11-14⏱ 9 دقائق قراءة
في المقال السابق، تعلمت App Router. الآن سنتعمق في **Server Components** — وهي **أكبر ميزة** في Next.js 15، وتغيّر طريقة تفكيرك في بناء التطبيقات.
في هذا الدليل العملي، سنأخذك خطوة بخطوة لفهم Server Components، مع تمارين وحلول.
## المشكلة التي تحلها Server Components
**قبل Server Components:**
```tsx
// كل شيء في المتصفح
function UserProfile() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch("/api/user")
.then((res) => res.json())
.then(setUser);
}, []);
if (!user) return <div>جاري التحميل...</div>;
return <h1>{user.name}</h1>;
}
```
**المشاكل:**
- ❌ الكود يُرسل للمتصفح.
- ❌ 3 مراحل (تحميل، fetch، عرض).
- ❌ بيانات لا تظهر لـ SEO.
- ❌ Bundle كبير.
**بعد Server Components:**
```tsx
// يعمل على السيرفر
async function UserProfile() {
const user = await getUser(); // مباشرة من DB
return <h1>{user.name}</h1>;
}
```
**الحلول:**
- ✅ الكود لا يُرسل للمتصفح.
- ✅ مرحلة واحدة.
- ✅ بيانات لـ SEO.
- ✅ Bundle أصغر.
## الفرق الأساسي
<table>
<thead>
<tr>
<th>المعيار</th>
<th>Server Component</th>
<th>Client Component</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>الافتراضي</strong></td>
<td>✅ نعم</td>
<td>❌ يحتاج "use client"</td>
</tr>
<tr>
<td><strong>useState</strong></td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td><strong>useEffect</strong></td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td><strong>onClick</strong></td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td><strong>async/await</strong></td>
<td>✅ مباشرة</td>
<td>❌ (في useEffect)</td>
</tr>
<tr>
<td><strong>الوصول لقاعدة البيانات</strong></td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td><strong>الوصول للملفات</strong></td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td><strong>البيانات الحساسة</strong></td>
<td>✅ آمن</td>
<td>❌ مكشوف</td>
</tr>
<tr>
<td><strong>Bundle Size</strong></td>
<td>✅ صفر</td>
<td>⚠️ يزيد</td>
</tr>
</tbody>
</table>
## Server Components
**افتراضياً، كل مكون في `app/` هو Server Component.**
### 1. جلب بيانات من API
```tsx
// app/users/page.tsx
async function getUsers() {
const res = await fetch("https://jsonplaceholder.typicode.com/users", {
cache: "no-store", // ← لا تخزين
});
return res.json();
}
export default async function UsersPage() {
const users = await getUsers();
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
```
**⚠️ لاحظ:**
- `async` مباشرة.
- `await` في المكوّن.
- لا `useState` ولا `useEffect`.
### 2. الوصول لقاعدة البيانات
```tsx
// app/posts/page.tsx
import { prisma } from "@/lib/prisma";
export default async function PostsPage() {
const posts = await prisma.post.findMany();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
```
**الفائدة:** لا تحتاج API وسيط — **مباشرة من DB**.
### 3. الوصول للملفات
```tsx
// app/data/page.tsx
import fs from "fs/promises";
export default async function DataPage() {
const content = await fs.readFile("./data.json", "utf-8");
const data = JSON.parse(content);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
```
## Client Components
**للاستخدام Client Component، أضف `"use client"` في أعلى الملف.**
### 1. استخدام useState
```tsx
// app/counter/page.tsx
"use client";
import { useState } from "react";
export default function CounterPage() {
const [count, setCount] = useState(0);
return (
<div>
<p>العدد: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
```
### 2. استخدام useEffect
```tsx
"use client";
import { useState, useEffect } from "react";
export default function Clock() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(timer);
}, []);
return <p>{time.toLocaleTimeString("ar-EG")}</p>;
}
```
### 3. استخدام onMouseOver
```tsx
"use client";
export default function HoverCard() {
return (
<div
onMouseOver={() => console.log("Hover!")}
className="p-4 bg-blue-100"
>
مرر الفأرة
</div>
);
}
```
## متى تستخدم كل نوع؟
<table>
<thead>
<tr>
<th>الحالة</th>
<th>النوع</th>
</tr>
</thead>
<tbody>
<tr>
<td>عرض بيانات ثابتة</td>
<td>Server</td>
</tr>
<tr>
<td>جلب من API/DB</td>
<td>Server</td>
</tr>
<tr>
<td>قراءة ملفات</td>
<td>Server</td>
</tr>
<tr>
<td>بيانات حساسة (API Keys)</td>
<td>Server</td>
</tr>
<tr>
<td>useState / useEffect</td>
<td>Client</td>
</tr>
<tr>
<td>onClick / onChange</td>
<td>Client</td>
</tr>
<tr>
<td>Browser APIs</td>
<td>Client</td>
</tr>
<tr>
<td>Context / Providers</td>
<td>Client</td>
</tr>
</tbody>
</table>
## تركيب المكونات
### 1. Server داخل Server
```tsx
// app/dashboard/page.tsx
import Stats from "./Stats";
import RecentOrders from "./RecentOrders";
export default async function Dashboard() {
return (
<div>
<Stats />
<RecentOrders />
</div>
);
}
```
**✅ يعمل** — Server داخل Server.
### 2. Client داخل Server
```tsx
// app/page.tsx
import LikeButton from "./LikeButton"; // ← "use client"
export default function Home() {
return (
<div>
<h1>مقالي</h1>
<LikeButton />
</div>
);
}
```
**✅ يعمل** — Client داخل Server.
### 3. Server داخل Client (مشكلة!)
```tsx
// ❌ خطأ:
"use client";
import ServerComponent from "./ServerComponent";
export default function ClientWrapper() {
return <ServerComponent />; // ← لن يعمل
}
```
**❌ لا يعمل** — Server داخل Client.
### 4. الحل: تمرير Server كـ children
```tsx
// ✅ صحيح:
"use client";
export default function ClientWrapper({
children,
}: {
children: React.ReactNode;
}) {
return <div>{children}</div>;
}
```
```tsx
// app/page.tsx (Server)
import ClientWrapper from "./ClientWrapper";
import ServerComponent from "./ServerComponent";
export default function Page() {
return (
<ClientWrapper>
<ServerComponent />
</ClientWrapper>
);
}
```
**✅ يعمل!** — Server يُمرَّر كـ `children` لـ Client.
## مثال عملي: صفحة منتج
### 1. Server Component (جلب البيانات)
**`app/products/[id]/page.tsx`:**
```tsx
import AddToCartButton from "./AddToCartButton";
async function getProduct(id: string) {
const res = await fetch(`https://api.example.com/products/${id}`);
return res.json();
}
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const product = await getProduct(id);
return (
<div className="max-w-4xl mx-auto p-8">
<img src={product.image} alt={product.name} className="w-full h-96 object-cover rounded-xl" />
<h1 className="text-3xl font-bold mt-6">{product.name}</h1>
<p className="text-gray-600 mt-4">{product.description}</p>
<p className="text-2xl font-bold text-blue-600 mt-4">
{product.price} $
</p>
<AddToCartButton productId={product.id} />
</div>
);
}
```
### 2. Client Component (زر الإضافة)
**`app/products/[id]/AddToCartButton.tsx`:**
```tsx
"use client";
import { useState } from "react";
export default function AddToCartButton({
productId,
}: {
productId: string;
}) {
const [added, setAdded] = useState(false);
const handleClick = () => {
// ... منطق إضافة للسلة
setAdded(true);
setTimeout(() => setAdded(false), 2000);
};
return (
<button
onClick={handleClick}
disabled={added}
className="mt-6 bg-blue-600 text-white px-8 py-3 rounded-lg font-bold hover:bg-blue-700 disabled:bg-green-600 transition"
>
{added ? "✅ تمت الإضافة" : "🛒 أضف للسلة"}
</button>
);
}
```
**الفائدة:** الصفحة Server (سريعة، SEO)، والزر Client (تفاعلي).
## Streaming و Suspense
**Suspense** تسمح بتحميل أجزاء الصفحة **بالتدريج**.
```tsx
import { Suspense } from "react";
export default function Dashboard() {
return (
<div>
<h1>لوحة التحكم</h1>
<Suspense fallback={<p>جاري تحميل الإحصائيات...</p>}>
<Stats />
</Suspense>
<Suspense fallback={<p>جاري تحميل الطلبات...</p>}>
<RecentOrders />
</Suspense>
</div>
);
}
```
**الفائدة:** الصفحة تبدأ بالظهور، والأجزاء البطيئة تُحمّل لاحقاً.
## cache في fetch
```tsx
// تخزين دائم (Static)
fetch(url, { cache: "force-cache" }); // افتراضي
// لا تخزين (Dynamic)
fetch(url, { cache: "no-store" });
// إعادة التحقق كل 60 ثانية (ISR)
fetch(url, { next: { revalidate: 60 } });
```
<table>
<thead>
<tr>
<th>الإعداد</th>
<th>السلوك</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>force-cache</code></td>
<td>تخزين دائم</td>
</tr>
<tr>
<td><code>no-store</code></td>
<td>لا تخزين (دائماً جديد)</td>
</tr>
<tr>
<td><code>revalidate: 60</code></td>
<td>إعادة التحقق كل 60 ثانية</td>
</tr>
</tbody>
</table>
## تمارين عملية
### تمرين 1: Server Component
أنشئ صفحة تعرض قائمة مستخدمين.
**الحل:**
```tsx
async function getUsers() {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
return res.json();
}
export default async function UsersPage() {
const users = await getUsers();
return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}
```
### تمرين 2: Client Component
أنشئ زر تبديل الوضع الداكن.
**الحل:**
```tsx
"use client";
import { useState } from "react";
export default function ThemeToggle() {
const [dark, setDark] = useState(false);
return (
<button onClick={() => setDark(!dark)}>
{dark ? "☀️" : "🌙"}
</button>
);
}
```
### تمرين 3: مزج Server + Client
أنشئ صفحة مقالات مع زر "Like".
**الحل:**
```tsx
// Server
async function getPosts() {
// ...
}
export default async function Posts() {
const posts = await getPosts();
return (
<ul>
{posts.map((p) => (
<li key={p.id}>
{p.title}
<LikeButton postId={p.id} />
</li>
))}
</ul>
);
}
```
### تمرين 4: Suspense
أضف Suspense لصفحة.
**الحل:**
```tsx
import { Suspense } from "react";
<Suspense fallback={<p>جاري التحميل...</p>}>
<SlowComponent />
</Suspense>
```
### تمرين 5: cache
جرّب cache: "no-store".
**الحل:**
```tsx
const res = await fetch(url, { cache: "no-store" });
```
### تمرين 6: Server مع DB
اجلب بيانات من ملف.
**الحل:**
```tsx
import fs from "fs/promises";
export default async function DataPage() {
const content = await fs.readFile("./data.json", "utf-8");
return <pre>{content}</pre>;
}
```
### تمرين 7: Client مع useEffect
أنشئ ساعة رقمية.
**الحل:**
```tsx
"use client";
import { useState, useEffect } from "react";
export default function Clock() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(timer);
}, []);
return <p>{time.toLocaleTimeString("ar-EG")}</p>;
}
```
### تمرين 8: تطبيق مختلط
ابنِ تطبيق يستخدم Server و Client معاً.
**الحل:** (راجع مثال صفحة المنتج)
## حل المشاكل الشائعة
### 🔴 المشكلة 1: `useState is not defined`
**السبب:** استخدمت `useState` في Server Component.
**الحل:** أضف `"use client"`.
### 🔴 المشكلة 2: `async/await` في Client
**السبب:** Client Component لا يمكن أن يكون `async`.
**الحل:** استخدم `useEffect` لجلب البيانات.
### 🔴 المشكلة 3: Server داخل Client
**السبب:** حاولت استيراد Server Component في Client Component.
**الحل:** مرّره كـ `children`.
### 🔴 المشكلة 4: `window is not defined`
**السبب:** استخدمت `window` في Server.
**الحل:** استخدم `"use client"` أو `typeof window !== "undefined"`.
### 🔴 المشكلة 5: البيانات لا تُحدّث
**السبب:** `cache` يخزّن البيانات.
**الحل:**
```tsx
fetch(url, { cache: "no-store" });
```
## جدول مقارنة سريعة
<table>
<thead>
<tr>
<th>الميزة</th>
<th>Server</th>
<th>Client</th>
</tr>
</thead>
<tbody>
<tr>
<td>async/await</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>useState</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>useEffect</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>onClick</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>DB Access</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>FS Access</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>Bundle Size</td>
<td>0 KB</td>
<td>يزيد</td>
</tr>
</tbody>
</table>
## قائمة تحقق نهائية
<table>
<thead>
<tr>
<th>المهمة</th>
<th>الحالة</th>
</tr>
</thead>
<tbody>
<tr>
<td>فهم Server vs Client</td>
<td>⬜</td>
</tr>
<tr>
<td>Server Component مع fetch</td>
<td>⬜</td>
</tr>
<tr>
<td>Client Component مع useState</td>
<td>⬜</td>
</tr>
<tr>
<td>مزج Server + Client</td>
<td>⬜</td>
</tr>
<tr>
<td>Suspense</td>
<td>⬜</td>
</tr>
<tr>
<td>cache في fetch</td>
<td>⬜</td>
</tr>
<tr>
<td>حل التمارين الثمانية</td>
<td>⬜</td>
</tr>
</tbody>
</table>
## ماذا بعد هذا المقال؟
الآن بعد أن أتقنت Server Components، أنت جاهز للمقال التالي:
1. **Data Fetching** — جلب البيانات المتقدم.
2. **API Routes** — بناء APIs.
3. **Authentication** — المصادقة.
## الخلاصة
في هذا المقال، تعلمت:
- ✅ ما هي Server Components.
- ✅ Server vs Client Components.
- ✅ `"use client"`.
- ✅ مزج Server + Client.
- ✅ Suspense و Streaming.
- ✅ cache في fetch.
- ✅ حل 8 تمارين عملية.
**تذكر:** Server Components هي **الميزة الأكبر** في Next.js 15 — استخدمها بحكمة.