Next.js: النموذج الذهني لمكونات الخادم (Server Components)
آخر تحديث: 2026-08-26
النموذج الذهني لـ RSC هو أهم تحول نموذجي في Next.js 16 — فقط بفهمه يمكنك استيعاب فلسفة التصميم وراء App Router.
1. ما ستتعلمه
- تعريف RSC وكيفية عمل "Zero-Client JS"
- حدود التصيير بين Server Component و Client Component
- توجيه
'use client'وقواعد اختراق حدود العميل (Client Boundary) - قواعد المكونات المتداخلة: Server → Client مسموح؛ Client → Server غير مسموح
- قيود Props القابلة للتسلسل (Serializable) وصيغة React Flight Payload
2. قصة حقيقية لمطور Full-Stack
(1) نقطة الألم: حجم الحزمة (Bundle Size) خارج السيطرة
Alice هي مطورة Full-Stack في فريق TaskFlow. صفحة Dashboard التي بنتها تتضمن مكون جدول بيانات يستخدم ثلاث مكتبات — date-fns وrecharts وlodash — فقط لتنسيق التواريخ ورسم بعض المخططات الشريطية. تضخمت حزمة JS الأولية للصفحة إلى 480 KB، وانخفضت درجة Lighthouse Performance إلى 52. والأسوأ من ذلك، أن هذا الجدول مجرد مكون تقديمي يُصيّر على الخادم — لا يحتاج المستخدمون للتفاعل معه إطلاقًا — ومع ذلك لا يزال كود هذه المكتبات يُنزّل إلى المتصفح.
(2) الحل عبر RSC
RSC يضمن أن مكونات الخادم تعمل فقط على الخادم، وتُخرج HTML خالصًا وبيانات متسلسلة، مع استبعاد حزم JavaScript تمامًا.
// app/dashboard/page.tsx — Server Component (Zero-Client JS)
import { getSalesData } from '@/lib/db'
import { formatDistanceToNow } from 'date-fns'
export default async function DashboardPage() {
const sales = await getSalesData() // الوصول المباشر إلى قاعدة البيانات
return (
<div>
<h1>Dashboard — {sales.length} سجل</h1>
<SalesTable data={sales} />
</div>
)
}
async function SalesTable({ data }: { data: Sale[] }) {
return (
<table>
{data.map(row => (
<tr key={row.id}>
<td>{formatDistanceToNow(row.createdAt)}</td>
<td>{row.amount}</td>
</tr>
))}
</table>
)
}
(3) النتائج
| البُعد | Pure Client Component | RSC |
|---|---|---|
| حجم الحزمة (Bundle Size) | 480 KB (يتضمن date-fns + ReCharts + Lodash) | 0 KB (مكتبات الخادم لا تُنزّل) |
| الوصول إلى قاعدة البيانات | يتطلب وسيط API Route | وصول مباشر (زمن انتقال صفري) |
| تصيير المحتوى الظاهر أولًا (Above-the-fold) | يتطلب تنزيل JS + تنفيذه | HTML فوري |
| SEO | يعتمد على SSR / التصيير من جانب العميل | دعم أصلي |
| درجة Lighthouse | 52 | 96 |
3. التعريف الأساسي لـ RSC
RSC (React Server Component) هو نوع مكونات جديد قُدّم في React 19، يعمل فقط على الخادم ولا يُرسل أبدًا إلى متصفح العميل. كود RSC (بما في ذلك المكتبات التابعة) لا يظهر في حزمة JS، لذا يمكنك استخدام مكتبات كبيرة بأمان، والوصول مباشرة إلى قواعد البيانات، وقراءة نظام الملفات.
graph TB
subgraph "جانب الخادم (Server)"
A[RSC Components] --> B[قاعدة البيانات/نظام الملفات/API]
A --> C[تسلسل إلى RSC Payload<br/>بروتوكول React Flight]
end
subgraph "العميل (Browser)"
D[RSC Payload] --> E[Client Component<br/>الحفاظ على منطق التفاعل]
D --> F[تصيير HTML خالص<br/>صفر تكلفة JS]
end
C --> D
style A fill:#d4edda
style D fill:#cce5ff
| الخاصية | Server Component | Client Component |
|---|---|---|
| بيئة التشغيل | جانب الخادم (Node.js) | المتصفح |
| حزمة JS | ❌ غير مضمنة | ✅ مضمنة |
| قاعدة البيانات/نظام الملفات | ✅ وصول مباشر | ❌ غير متاح (يتطلب API) |
| React Hooks (useState/useEffect) | ❌ غير متاحة | ✅ متاحة |
| معالجة الأحداث (onClick/onSubmit) | ❌ غير متاحة | ✅ متاحة |
| Async/Await | ✅ دعم أصلي | ❌ يتطلب معالجة إضافية |
(1) معنى "Zero-Client JS"
المبدأ الأساسي لـ RSC هو: إذا كان المكون لا يحتوي على منطق تفاعلي، فلا ينبغي إرسال كوده إلى المتصفح. هذا يعني:
- عند استخدامها في RSC، المكتبات المخصصة للخادم فقط مثل
date-fnsوlodashوbcryptلا تزيد من حجم الحزمة - استعلامات قاعدة البيانات تُنفذ مباشرة داخل المكون، دون المرور عبر API Route
- RSC يُخرج في النهاية سلسلة HTML خالصة، يقوم المتصفح بتصييرها فورًا
(2) RSC مقابل SSR التقليدي
SSR التقليدي (Page Router) يُصيّر أيضًا HTML على الخادم، لكنه لا يزال يرسل كود JavaScript الخاص بالمكون إلى العميل من أجل hydration. أما RSC فمختلف تمامًا — كود مكونات الخادم لا يصل أبدًا إلى العميل.
| البُعد | SSR التقليدي (Pages Router) | RSC (App Router) |
|---|---|---|
| التصيير من جانب الخادم | ✅ HTML | ✅ HTML |
| Client Hydration | ✅ كامل | ❌ غير مطلوب |
| إرسال كود المكون إلى العميل | ✅ الكل | ❌ Client Component فقط |
| الحفاظ على الحالة (State) | يتطلب معالجة دقيقة | بلا حالة بطبيعته (Stateless) |
| توقيت استرجاع البيانات | getServerSideProps |
مباشرة داخل المكون await |
▶ مثال: التحقق من فروق حجم الحزمة (مستوى الصعوبة: ⭐⭐)
المخرجات:
الرسم التخطيطي: RSC Components؛ قاعدة البيانات/نظام الملفات/API؛ تسلسل إلى RSC Payload بروتوكول React Flight؛ RSC Payload؛ Client Component الحفاظ على منطق التفاعل؛ تصيير HTML خالص صفر تكلفة JS.
// app/bundle-demo/page.tsx — Server Component (خادم بحت)
import { format, addDays } from 'date-fns'
export default function BundleDemoPage() {
const today = new Date()
const dates = Array.from({ length: 7 }, (_, i) => {
const d = addDays(today, i)
return { label: format(d, 'EEEE'), date: format(d, 'yyyy-MM-dd') }
})
return (
<div>
<h1>هذا الأسبوع</h1>
<ul>{dates.map(d => <li key={d.date}>{d.label}: {d.date}</li>)}</ul>
</div>
)
}
المخرجات:
فحص ما بعد البناء .next/static/chunks — لا يتضمن كود date-fns
المخرجات:
بعد البناء، لا يحتوي مجلد .next/static/chunks على كود date-fns — تم تأكيد صفر تكلفة JS من جانب العميل. الصفحة تُصيّر: عنوان "هذا الأسبوع" + 7 عناصر قائمة (مثل "الاثنين: 2026-07-06"، "الثلاثاء: 2026-07-07"، ...).
4. توجيه 'use client' وحدود العميل (Client Boundaries)
'use client' هو توجيه على مستوى الوحدة (module-level) يعلّم المكون في الملف كمكون عميل (Client Component). عندما تكون هناك حاجة لوظائف تفاعلية (useState، onClick، useEffect) في شجرة مكونات RSC، يجب إضافة هذا التوجيه في أعلى الملف.
graph TB
A[Root Layout<br/>Server Component] --> B[NavBar<br/>Server Component]
A --> C[DashboardPage<br/>Server Component]
C --> D[SalesChart<br/>'use client']
C --> E[DataTable<br/>Server Component]
D --> F[منطق التفاعل<br/>useState / useEffect]
style A fill:#d4edda
style B fill:#d4edda
style C fill:#d4edda
style D fill:#cce5ff
style E fill:#d4edda
| التوجيه | الوظيفة | مثال |
|---|---|---|
'use client' |
تعليم الوحدة كمكون عميل (Client Component) | 'use client'; export default function Btn() { ... } |
'use server' |
تعليم الدالة كـ Server Action | 'use server'; export async function create() { ... } |
(1) قواعد اختراق الحدود
هناك قاعدتان صارمتان في شجرة مكونات RSC:
- ✅ يمكن لمكونات الخادم استيراد وتصيير مكونات العميل
- ❌ لا يمكن لمكونات العميل استيراد مكونات الخادم مباشرة (لأن مكونات الخادم موجودة فقط على جانب الخادم)
// ✅ صحيح: Server Component يستورد Client Component
// app/page.tsx (Server)
import ClientCounter from './ClientCounter'
export default function Page() {
return <ClientCounter />
}
// app/ClientCounter.tsx (Client)
'use client'
import { useState } from 'react'
export default function ClientCounter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
// ❌ خطأ: Client Component لا يمكنه استيراد Server Component مباشرة
// app/ClientList.tsx
'use client'
import ServerItem from './ServerItem' // ❌ خطأ ترجمة: Server Component لا يمكن استيراده من جانب العميل
export default function ClientList() {
return <ServerItem /> // هذا السطر سيسبب خطأ.
}
(2) كيفية تضمين Server Component داخل Client Component
نصيحة: مرر البيانات عبر children props — فتحة children في مكون العميل يمكنها استقبال نتيجة التصيير من مكون الخادم.
// app/layout.tsx (Server)
import ClientShell from './ClientShell'
import ServerSidebar from './ServerSidebar'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<ClientShell sidebar={<ServerSidebar />}>
{children}
</ClientShell>
)
}
▶ مثال: الطريقة الصحيحة للتعامل مع تمرير Props — Children Props (مستوى الصعوبة: ⭐⭐)
المخرجات:
يتضمن شريطًا جانبيًا.
النص المرئي: }>
{children}
// app/interleaving/ClientWrapper.tsx
'use client'
import { useState } from 'react'
export default function ClientWrapper({ sidebar, children }: {
sidebar: React.ReactNode
children: React.ReactNode
}) {
const [isOpen, setIsOpen] = useState(true)
return (
<div style={{ display: 'flex' }}>
{isOpen && <aside>{sidebar}</aside>}
<button onClick={() => setIsOpen(!isOpen)}>تبديل</button>
<main>{children}</main>
</div>
)
}
المخرجات:
مكون تفاعلي مع إدارة حالة.
النص المرئي: }
// app/interleaving/page.tsx (Server)
import ClientWrapper from './ClientWrapper'
import { getSidebarData } from '@/lib/db'
export default function InterleavingPage() {
const items = getSidebarData() // استرجاع البيانات على جانب الخادم
return (
<ClientWrapper sidebar={<ServerItemList items={items} />}>
<h1>المحتوى الرئيسي</h1>
</ClientWrapper>
)
}
async function ServerItemList({ items }: { items: string[] }) {
return <ul>{items.map(i => <li key={i}>{i}</li>)}</ul>
}
▶ مثال: قيود Props القابلة للتسلسل (مستوى الصعوبة: ⭐⭐⭐)
المخرجات:
يُصيّر قائمة من العناصر باستخدام .map().
النص المرئي: }> | المحتوى الرئيسي
// app/serializable/page.tsx (Server)
function greet() { return 'hello' } // ❌ الدوال غير قابلة للتسلسل
const date = new Date() // ⚠️ Date غير مسموح به كـ RSC Props
// app/serializable/ClientComponent.tsx
'use client'
export default function ClientComponent(props: {
fn: () => string // ❌ الدوال كـ props → خطأ وقت التشغيل
date: Date // ⚠️ Date → يتم تحويله إلى نص قد يفقد المنطقة الزمنية
data: { name: string } // ✅ الكائنات العادية مسموحة
}) {
return <div>{props.data.name}</div>
}
المخرجات:
يُصيّر واجهة مكون ClientComponent.
5. دمج React Flight Payload مع شجرة المكونات
بعد اكتمال تصيير RSC من جانب الخادم، يُخرج تنسيق بيانات خاص يسمى RSC Payload (بروتوكول React Flight)، والذي يحتوي على شجرة HTML المتسلسلة، ومراجع المكونات، وبيانات props. عندما يستقبل العميل الـ payload، يدمجه مع مكون العميل المحلي لتشكيل شجرة المكونات النهائية.
(1) هيكل RSC Payload
sequenceDiagram
participant Server as خادم Next.js
participant Client as المتصفح
Server->>Server: تنفيذ شجرة مكونات RSC
Server->>Server: تسلسل إلى React Flight Payload
Server->>Client: إرسال RSC Payload + HTML
Client->>Client: تحليل Flight Payload
Client->>Client: دمج Client Component (Hydrate)
Client->>Client: تصيير الواجهة النهائية
| المكون | الوصف | مثال |
|---|---|---|
| مخرجات Server Component | جزء HTML متسلسل | <div><h1>Dashboard</h1></div> |
| مرجع Client Component | معرف الوحدة + Props | {id: "./chart.js", props: {data: [...]}} |
| مرجع البيانات | نتائج استعلام قاعدة البيانات | {sales: [{id:1, amount: 100}]} |
| التدفق (Stream) | تقسيم حدود Suspense | إرسال أجزاء متعددة بشكل تدريجي |
▶ مثال: عرض RSC Payload (مستوى الصعوبة: ⭐⭐)
اعرض استجابة RSC في لوحة Network بأدوات المطور في المتصفح:
# افتح لوحة Network، قم بتحديث الصفحة، فلتر Fetch/XHR
# ابحث عن طلب الصفحة الحالية، اعرض Response
# Content-Type: text/x-component أي RSC Payload
المخرجات:
# مقتطف من RSC Payload (مبسط):
M1:{"id":"./app/page.tsx","chunks":["app/page-abc123.js"]}
J0:["$","div",null,{"children":["$","h1",null,{"children":"Dashboard"}]}]
S1:"react.suspense"
▶ مثال: كيف يشير Client Component إلى Server Component (مستوى الصعوبة: ⭐⭐⭐)
// app/flight-demo/ServerData.tsx — مكون بيانات خادم بحت
export default async function ServerData() {
const data = await fetch('https://api.example.com/data').then(r => r.json())
return <pre>{JSON.stringify(data, null, 2)}</pre>
}
// app/flight-demo/ClientShell.tsx
'use client'
export default function ClientShell({ dataSlot }: { dataSlot: React.ReactNode }) {
return (
<div className="card">
<h2>غلاف العميل (Client Shell)</h2>
<div className="server-data">{dataSlot}</div>
</div>
)
}
المخرجات:
يُصيّر: غلاف العميل (Client Shell)
النص المرئي: Client Shell
// app/flight-demo/page.tsx
import ClientShell from './ClientShell'
import ServerData from './ServerData'
export default function FlightDemoPage() {
return (
<ClientShell dataSlot={<ServerData />}>
)
}
6. مثال كامل: بنية شجرة مكونات RSC
// app/rsc-architecture/layout.tsx — RSC Layout
import ClientShell from './ClientShell'
import { getUser } from '@/lib/auth'
export default async function RscLayout({ children }: { children: React.ReactNode }) {
const user = await getUser() // ✅ استعلام مباشر لقاعدة البيانات
return (
<ClientShell username={user?.name ?? 'زائر'}>
<nav>
<a href="/">الرئيسية</a>
<a href="/dashboard">لوحة التحكم</a>
<a href="/settings">الإعدادات</a>
</nav>
{children}
</ClientShell>
)
}
// app/rsc-architecture/ClientShell.tsx
'use client'
import { useState } from 'react'
import type { ReactNode } from 'react'
export default function ClientShell({ username, children }: {
username: string
children: ReactNode
}) {
const [theme, setTheme] = useState<'light' | 'dark'>('light')
return (
<div data-theme={theme}>
<header>
<span>مرحبًا، {username}</span>
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
تبديل {theme}
</button>
</header>
{children}
</div>
)
}
// app/rsc-architecture/page.tsx
import { getProjects } from '@/lib/db'
export default async function RscArchitecturePage() {
const projects = await getProjects()
return (
<div>
<h1>المشاريع ({projects.length})</h1>
<table>
<thead><tr><th>الاسم</th><th>الحالة</th></tr></thead>
<tbody>
{projects.map(p => (
<tr key={p.id}>
<td>{p.name}</td>
<td><StatusBadge status={p.status} /></td>
</tr>
))}
</tbody>
</table>
</div>
)
}
function StatusBadge({ status }: { status: string }) {
const colors: Record<string, string> = {
active: '#4caf50', archived: '#9e9e9e', draft: '#ff9800'
}
return <span style={{ background: colors[status] ?? '#ccc', padding: '2px 8px', borderRadius: 4 }}>{status}</span>
}
❓ أسئلة شائعة
children prop أو Server Actions.📖 ملخص
- RSC (React Server Component) يعمل فقط على الخادم، مع صفر تكلفة JavaScript من جانب العميل
'use client'يُحدد حدود Client Component؛ منطق التفاعل يجب أن يكون على جانب العميل- يمكن لـ Server Component استيراد Client Component، لكن ليس العكس (يمكن تجاوز ذلك باستخدام
childrenprop) - قيود Props القابلة للتسلسل: الدوال، وكائنات Date، و
undefinedلا يمكن تمريرها كـ RSC props - React Flight Payload هو بروتوكول تسلسل RSC، ويحتوي على مقاطع HTML، ومراجع المكونات، والبيانات
- أفضل ممارسة: استخدم Server Components قدر الإمكان، واعزل منطق التفاعل في أغلفة Client صغيرة
📝 تمارين
-
تمرين أساسي (⭐): أنشئ Server Component في
app/page.tsxيستدعي مباشرةfetch('https://api.github.com/repos/vercel/next.js')لاسترجاع البيانات وعرض عدد النجوم (star rating). تحقق من أن العميل لا يُنزّل مكتبات مثلnode-fetch. -
تمرين متقدم (⭐⭐): أنشئ صفحة تحتوي على Client Component (زر عداد) و Server Component (قائمة مستخدمين)، ومرر بيانات الخادم إلى غلاف العميل باستخدام
childrenprop. تحقق من استجابة RSC Payload في تبويب Network بأدوات مطور المتصفح. -
تحدٍّ (⭐⭐⭐): ابنِ شجرة مكونات من ثلاث طبقات: Layout (Server) → ClientTabs (Client، يحتوي على
useStateلإدارة التبويب الحالي) → ServerTabContent (Server Components تُمرر عبرchildren)، حيث يكون محتوى كل تبويب استعلام بيانات غير متزامن مختلف. تأكد من أن جلب البيانات من جميع Server Components لا يزيد من حجم حزمة العميل.