مرز Server/Client

در دو درس قبل، Server Component و Client Component را جداگانه دیدیم. اما در یک برنامه واقعی، این دو در یک درخت واحد کنار هم قرار می‌گیرند. جایی که یکی به دیگری می‌رسد، «مرز Server/Client» است. این درس توضیح می‌دهد این مرز دقیقاً کجا کشیده می‌شود و چه چیزی از آن عبور می‌کند.

مرز با «use client» تعیین می‌شود

همان‌طور که در درس قبل دیدیم، نوشتن "use client" بالای یک فایل، آن فایل را نقطه ورود به دنیای Client می‌کند. به همین دلیل به آن «مرز» می‌گویند: بالای آن (در سمت Server) کدها فقط روی سرور اجرا می‌شوند و پایین آن (در سمت Client) کدها به مرورگر فرستاده می‌شوند.

مرز در گراف import تعریف می‌شود، نه در درخت رندر

این مهم‌ترین نکته درس است. دستور "use client" روی فایل‌ها و importها اثر می‌گذارد. هر ماژولی که از یک فایل Client وارد شود، حتی اگر دستور خودش را نداشته باشد، بخشی از بسته Client می‌شود:

jsx
// Sidebar.js
"use client";

import Recommendations from "./Recommendations";
// Recommendations هم از این‌جا به بعد Client Component است

از طرف دیگر، لازم نیست در هر فایلی که زیر یک فایل Client قرار دارد، دوباره "use client" بنویسید؛ یک بار در نقطه ورود کافی است.

جهت مجاز: Server می‌تواند Client را import کند

یک Server Component آزادانه می‌تواند Client Component را import و رندر کند (مثل LikeButton در درس قبل). عکس آن اما ممکن نیست: اگر یک Client Component یک کامپوننت را import کند، آن کامپوننت دیگر Server نیست و Client می‌شود. برای مثال، اگر آن کامپوننت مستقیماً به پایگاه داده دسترسی داشته باشد، دیگر نمی‌تواند در مرورگر اجرا شود و خطا می‌دهد (یا بدتر، کد حساس به مرورگر نشت می‌کند). بعضی ابزارها و بسته‌ها (مثل بسته server-only) برای همین موقعیت ساخته شده‌اند تا اگر کد مخصوص سرور اشتباهاً وارد بسته Client شود، همان لحظه Build خطا بدهد.

راه‌حل: Server Component به‌عنوان children

پس آیا یک Client Component هرگز نمی‌تواند خروجی Server Component را نشان دهد؟ می‌تواند، به یک شرط: آن را خودش import نکند، بلکه از والدی که Server Component است، به‌عنوان children یا Prop دریافت کند. این همان الگوی ترکیب (Composition) فصل دوم و درس ۱-۶ است:

jsx
// Sidebar.js (Client Component)
"use client";

import { useState } from "react";

export default function Sidebar({ children }) {
  const [open, setOpen] = useState(true);

  return (
    <aside>
      <button onClick={() => setOpen(!open)}>نمایش یا پنهان</button>
      {open && children}
    </aside>
  );
}
jsx
// Page.js (Server Component)
import Sidebar from "./Sidebar";
import Recommendations from "./Recommendations";

export default async function Page() {
  return (
    <Sidebar>
      <Recommendations />
    </Sidebar>
  );
}

در این ساختار، Recommendations در فایل Page.js وارد شده که یک ماژول Server است. پس روی سرور رندر می‌شود و کدش به مرورگر نمی‌رود. Sidebar فقط خروجی آماده را به‌عنوان children می‌گیرد و باز و بسته کردن آن، که یک State محلی کلاینت است، بدون هیچ رفت‌وبرگشتی به سرور انجام می‌شود.

Context و Providerها در مرز

Context، چون به createContext و State وابسته است، یک قابلیت سمت Client است؛ پس Server Component نمی‌تواند Context بسازد. الگوی رایج این بود که یک فایل "use client" بسازیم که Provider را داخلش بنویسیم و children بگیرد. از React 19.3 (که در فصل قبل با آن آشنا شدیم)، یک Server Component می‌تواند خود Context را که در یک ماژول "use client" ساخته شده، مستقیم import و رندر کند، بدون کامپوننت واسطه‌ای که فقط Prop را به Context بدهد.

قاعده عملی برای کشیدن مرز

  • مرز را تا پایین‌ترین حد ممکن بیاورید، فقط دور بخشی که واقعاً تعاملی است (مثل اصل درس قبل).
  • اگر یک Client Component باید «محتوای دلخواه» را دور خود بپیچد (مثل Sidebar، Modal یا Tab)، آن محتوا را از بیرون با children بگیرید.
  • هر چیزی که به منبع سرور (پایگاه داده، کلید محرمانه) وابسته است، نباید زیر یک فایل "use client" import شود.

چرا این درس مثال قابل اجرا ندارد

مرز Server/Client فقط در پروژه‌ای با Bundler یا Framework پشتیبان Server Components وجود دارد، که در ویرایشگر درون‌صفحه‌ای در دسترس نیست. کدهای این درس را در یک پروژه واقعی با Framework مناسب اجرا کنید.

جمع‌بندی

مرز Server/Client با "use client" روی یک فایل تعیین می‌شود و در گراف import اثر دارد: هر چیزی که از یک فایل Client وارد شود، Client می‌شود. Server می‌تواند Client را import کند، ولی عکس آن درست نیست. اگر یک Client Component لازم است خروجی یک Server Component را نشان دهد، باید آن را از والد Server به‌صورت children یا Prop بگیرد، نه این‌که خودش import کند. در درس بعدی، به محدودیت داده‌هایی می‌رسیم که از این مرز عبور می‌کنند.