كيفية استخدام مكونات الخادم والعميل
بشكل افتراضي، تعتبر التخطيطات والصفحات مكونات خادم (Server Components)، مما يسمح لك بجلب البيانات وعرض أجزاء من واجهة المستخدم على الخادم، وتخزين النتيجة مؤقتًا إذا لزم الأمر، ودفقها إلى العميل. عندما تحتاج إلى تفاعلية أو واجهات برمجة تطبيقات المتصفح، يمكنك استخدام مكونات العميل (Client Components) لإضافة الوظائف.
توضح هذه الصفحة كيفية عمل مكونات الخادم والعميل في Next.js ومتى تستخدمها، مع أمثلة حول كيفية تجميعها معًا في تطبيقك.
متى تستخدم مكونات الخادم والعميل؟
بيئات الخادم والعميل لديها إمكانيات مختلفة. تسمح لك مكونات الخادم والعميل بتنفيذ المنطق في كل بيئة حسب حالة الاستخدام.
استخدم مكونات العميل عندما تحتاج إلى:
- الحالة (State) ومعالجات الأحداث (Event Handlers). مثل
onClick
،onChange
. - منطق دورة الحياة (Lifecycle Logic). مثل
useEffect
. - واجهات برمجة تطبيقات المتصفح فقط. مثل
localStorage
،window
،Navigator.geolocation
، إلخ. - خطافات مخصصة (Custom Hooks).
استخدم مكونات الخادم عندما تحتاج إلى:
- جلب البيانات من قواعد البيانات أو واجهات برمجة التطبيقات القريبة من المصدر.
- استخدام مفاتيح واجهات برمجة التطبيقات، الرموز المميزة، والأسرار الأخرى دون الكشف عنها للعميل.
- تقليل كمية JavaScript المرسلة إلى المتصفح.
- تحسين أول عرض للمحتوى (First Contentful Paint - FCP)، ودفق المحتوى تدريجيًا إلى العميل.
على سبيل المثال، مكون <Page>
هو مكون خادم يجلب بيانات حول منشور، ويمررها كخصائص إلى <LikeButton>
الذي يتعامل مع التفاعل من جانب العميل.
import LikeButton from '@/app/ui/like-button'
import { getPost } from '@/lib/data'
export default async function Page({ params }: { params: { id: string } }) {
const post = await getPost(params.id)
return (
<div>
<main>
<h1>{post.title}</h1>
{/* ... */}
<LikeButton likes={post.likes} />
</main>
</div>
)
}
import LikeButton from '@/app/ui/like-button'
import { getPost } from '@/lib/data'
export default async function Page({ params }) {
const post = await getPost(params.id)
return (
<div>
<main>
<h1>{post.title}</h1>
{/* ... */}
<LikeButton likes={post.likes} />
</main>
</div>
)
}
كيف تعمل مكونات الخادم والعميل في Next.js؟
على الخادم
على الخادم، يستخدم Next.js واجهات برمجة تطبيقات React لتنسيق العرض. يتم تقسيم عمل العرض إلى أجزاء، حسب أجزاء المسار الفردية (التخطيطات والصفحات):
- يتم عرض مكونات الخادم بتنسيق بيانات خاص يسمى حمولة مكون خادم React (RSC Payload).
- تُستخدم مكونات العميل وحمولة RSC لـالتقديم المسبق (Prerender) HTML.
ما هي حمولة مكون خادم React (RSC)؟
حمولة RSC هي تمثيل ثنائي مضغوط لشجرة مكونات خادم React المعروضة. يستخدمها React على العميل لتحديث نموذج كائن المستند (DOM) للمتصفح. تحتوي حمولة RSC على:
- نتيجة عرض مكونات الخادم
- عناصر نائبة لمكان عرض مكونات العميل ومراجع لملفات JavaScript الخاصة بها
- أي خصائص تم تمريرها من مكون خادم إلى مكون عميل
على العميل (أول تحميل)
ثم على العميل:
- يتم استخدام HTML لعرض معاينة سريعة غير تفاعلية للمسار للمستخدم فورًا.
- تُستخدم حمولة RSC لتوفيق أشجار مكونات العميل والخادم.
- يُستخدم JavaScript لترطيب (Hydrate) مكونات العميل وجعل التطبيق تفاعليًا.
ما هو الترطيب (Hydration)؟
الترطيب هو عملية React لإرفاق معالجات الأحداث (Event Handlers) بنموذج كائن المستند (DOM)، لجعل HTML الثابت تفاعليًا.
التنقلات اللاحقة
في التنقلات اللاحقة:
- يتم جلب حمولة RSC مسبقًا وتخزينها مؤقتًا للتنقل الفوري.
- يتم عرض مكونات العميل بالكامل على العميل، دون HTML المعروض من الخادم.
أمثلة
استخدام مكونات العميل
يمكنك إنشاء مكون عميل عن طريق إضافة التوجيه "use client"
في أعلى الملف، قبل الاستيرادات.
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>{count} likes</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>{count} likes</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
يُستخدم "use client"
للإعلان عن حدود بين الرسوم البيانية (الأشجار) لوحدات الخادم والعميل.
بمجرد وضع علامة على ملف بـ "use client"
، تعتبر جميع استيراداته والمكونات الفرعية جزءًا من حزمة العميل. هذا يعني أنك لست بحاجة إلى إضافة التوجيه إلى كل مكون مخصص للعميل.
تقليل حجم حزمة JS
لتقليل حجم حزم JavaScript الخاصة بالعميل، أضف 'use client'
إلى مكونات تفاعلية محددة بدلاً من وضع علامة على أجزاء كبيرة من واجهة المستخدم كمكونات عميل.
على سبيل المثال، يحتوي مكون <Layout>
على عناصر ثابتة في الغالب مثل شعار وروابط تنقل، ولكنه يتضمن شريط بحث تفاعلي. <Search />
تفاعلي ويجب أن يكون مكون عميل، بينما يمكن أن يبقى باقي التخطيط مكون خادم.
'use client'
export default function Search() {
// ...
}
'use client'
export default function Search() {
// ...
}
// مكون عميل
import Search from './search'
// مكون خادم
import Logo from './logo'
// التخطيط هو مكون خادم افتراضيًا
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<nav>
<Logo />
<Search />
</nav>
<main>{children}</main>
</>
)
}
// مكون عميل
import Search from './search'
// مكون خادم
import Logo from './logo'
// التخطيط هو مكون خادم افتراضيًا
export default function Layout({ children }) {
return (
<>
<nav>
<Logo />
<Search />
</nav>
<main>{children}</main>
</>
)
}
تمرير البيانات من مكونات الخادم إلى العميل
يمكنك تمرير البيانات من مكونات الخادم إلى مكونات العميل باستخدام الخصائص.
import LikeButton from '@/app/ui/like-button'
import { getPost } from '@/lib/data'
export default async function Page({ params }: { params: { id: string } }) {
const post = await getPost(params.id)
return <LikeButton likes={post.likes} />
}
import LikeButton from '@/app/ui/like-button'
import { getPost } from '@/lib/data'
export default async function Page({ params }) {
const post = await getPost(params.id)
return <LikeButton likes={post.likes} />
}
بدلاً من ذلك، يمكنك دفق البيانات من مكون خادم إلى مكون عميل باستخدام خطاف use
. انظر مثال.
جيد أن تعرف: يجب أن تكون الخصائص الممررة إلى مكونات العميل قابلة للتسلسل (Serializable) بواسطة React.
تداخل مكونات الخادم والعميل
يمكنك تمرير مكونات الخادم كخاصية إلى مكون عميل. هذا يسمح لك بتداخل واجهة المستخدم المعروضة من الخادم داخل مكونات العميل.
نمط شائع هو استخدام children
لإنشاء فتحة في <ClientComponent>
. على سبيل المثال، مكون <Cart>
الذي يجلب البيانات من الخادم، داخل مكون <Modal>
الذي يستخدم حالة العميل لتبديل الرؤية.
'use client'
export default function Modal({ children }: { children: React.ReactNode }) {
return <div>{children}</div>
}
'use client'
export default function Modal({ children }) {
return <div>{children}</div>
}
ثم، في مكون خادم أصل (مثل <Page>
)، يمكنك تمرير <Cart>
كطفل لـ <Modal>
:
import Modal from './ui/modal'
import Cart from './ui/cart'
export default function Page() {
return (
<Modal>
<Cart />
</Modal>
)
}
import Modal from './ui/modal'
import Cart from './ui/cart'
export default function Page() {
return (
<Modal>
<Cart />
</Modal>
)
}
في هذا النمط، سيتم عرض جميع مكونات الخادم على الخادم مسبقًا، بما في ذلك تلك التي تم تمريرها كخصائص. ستتضمن حمولة RSC الناتجة مراجع لمكان عرض مكونات العميل داخل شجرة المكونات.
موفرو السياق (Context Providers)
يُستخدم سياق React (React Context) بشكل شائع لمشاركة الحالة العامة مثل السمة الحالية. ومع ذلك، لا يتم دعم سياق React في مكونات الخادم.
لاستخدام السياق، أنشئ مكون عميل يقبل children
:
'use client'
import { createContext } from 'react'
export const ThemeContext = createContext({})
export default function ThemeProvider({
children,
}: {
children: React.ReactNode
}) {
return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
}
'use client'
import { createContext } from 'react'
export const ThemeContext = createContext({})
export default function ThemeProvider({ children }) {
return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
}
ثم، قم باستيراده في مكون خادم (مثل layout
):
import ThemeProvider from './theme-provider'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
)
}
import ThemeProvider from './theme-provider'
export default function RootLayout({ children }) {
return (
<html>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
)
}
سيتمكن مكون الخادم الخاص بك الآن من عرض موفرك مباشرة، وستتمكن جميع مكونات العميل الأخرى في تطبيقك من استهلاك هذا السياق.
جيد أن تعرف: يجب أن تعرض الموفرين في أعمق مكان ممكن في الشجرة - لاحظ كيف أن
ThemeProvider
يلف فقط{children}
بدلاً من مستند<html>
بأكمله. هذا يجعل من السهل على Next.js تحسين الأجزاء الثابتة من مكونات الخادم الخاصة بك.
مكونات الطرف الثالث
عند استخدام مكون طرف ثالث يعتمد على ميزات العميل فقط، يمكنك لفه في مكون عميل لضمان عمله كما هو متوقع.
على سبيل المثال، يمكن استيراد <Carousel />
من حزمة acme-carousel
. يستخدم هذا المكون useState
، ولكنه لا يملك بعد توجيه "use client"
.
إذا استخدمت <Carousel />
داخل مكون عميل، فسيعمل كما هو متوقع:
'use client'
import { useState } from 'react'
import { Carousel } from 'acme-carousel'
export default function Gallery() {
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<button onClick={() => setIsOpen(true)}>View pictures</button>
{/* يعمل، لأن Carousel مستخدم داخل مكون عميل */}
{isOpen && <Carousel />}
</div>
)
}
'use client'
import { useState } from 'react'
import { Carousel } from 'acme-carousel'
export default function Gallery() {
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<button onClick={() => setIsOpen(true)}>View pictures</button>
{/* يعمل، لأن Carousel مستخدم داخل مكون عميل */}
{isOpen && <Carousel />}
</div>
)
}
ومع ذلك، إذا حاولت استخدامه مباشرة داخل مكون خادم، فسترى خطأ. هذا لأن Next.js لا يعرف أن <Carousel />
يستخدم ميزات العميل فقط.
لإصلاح هذا، يمكنك لف مكونات الطرف الثالث التي تعتمد على ميزات العميل فقط في مكونات العميل الخاصة بك:
'use client'
import { Carousel } from 'acme-carousel'
export default Carousel
'use client'
import { Carousel } from 'acme-carousel'
export default Carousel
الآن، يمكنك استخدام <Carousel />
مباشرة داخل مكون خادم:
import Carousel from './carousel'
export default function Page() {
return (
<div>
<p>View pictures</p>
{/* يعمل، لأن Carousel هو مكون عميل */}
<Carousel />
</div>
)
}
import Carousel from './carousel'
export default function Page() {
return (
<div>
<p>View pictures</p>
{/* يعمل، لأن Carousel هو مكون عميل */}
<Carousel />
</div>
)
}
نصيحة لمؤلفي المكتبات
إذا كنت تبني مكتبة مكونات، أضف توجيه
"use client"
إلى نقاط الدخول التي تعتمد على ميزات العميل فقط. هذا يسمح لمستخدميك باستيراد المكونات في مكونات الخادم دون الحاجة إلى إنشاء أغلفة.من الجدير بالذكر أن بعض أدوات الحزم قد تحذف توجيهات
"use client"
. يمكنك العثور على مثال لكيفية تكوين esbuild لتضمين توجيه"use client"
في مستودعات React Wrap Balancer وVercel Analytics.
منع تلويث البيئة (Environment poisoning)
يمكن مشاركة وحدات جافا سريبت (JavaScript modules) بين وحدات مكونات الخادم والعميل (Server and Client Components). وهذا يعني أنه من الممكن استيراد كود مخصص للخادم فقط (server-only) إلى العميل عن طريق الخطأ. على سبيل المثال، ضع في الاعتبار الدالة التالية:
export async function getData() {
const res = await fetch('https://external-service.com/data', {
headers: {
authorization: process.env.API_KEY,
},
})
return res.json()
}
export async function getData() {
const res = await fetch('https://external-service.com/data', {
headers: {
authorization: process.env.API_KEY,
},
})
return res.json()
}
تحتوي هذه الدالة على API_KEY
التي لا يجب أن تصل إلى العميل أبدًا.
في Next.js، فقط متغيرات البيئة (environment variables) المسبوقة بـ NEXT_PUBLIC_
يتم تضمينها في حزمة العميل (client bundle). إذا لم تكن المتغيرات مسبوقة بهذا البادئة، يقوم Next.js باستبدالها بسلسلة فارغة.
نتيجة لذلك، على الرغم من أنه يمكن استيراد وتنفيذ getData()
على العميل، إلا أنها لن تعمل كما هو متوقع.
لمنع الاستخدام العرضي في مكونات العميل (Client Components)، يمكنك استخدام حزمة server-only
.
npm install server-only
ثم قم باستيراد الحزمة إلى ملف يحتوي على كود مخصص للخادم فقط:
import 'server-only'
export async function getData() {
const res = await fetch('https://external-service.com/data', {
headers: {
authorization: process.env.API_KEY,
},
})
return res.json()
}
الآن، إذا حاولت استيراد الوحدة إلى مكون عميل (Client Component)، سيظهر خطأ أثناء وقت البناء (build-time error).
معلومة مفيدة: يمكن استخدام الحزمة المقابلة
client-only
لوضع علامة على الوحدات التي تحتوي على منطق مخصص للعميل فقط (client-only logic) مثل الكود الذي يصل إلى كائنwindow
.