۱۴۰۴/۰۷/۲۷ Nebular

کامپوننت های client و server در nextjs

وقتی یک فایل "use client" دارد، تمام ایمپورت‌ها و فرزندان آن فایل در محدودهٔ client bundle قرار می‌گیرند — یعنی آن فایل و آن فرزندان به‌عنوان بخشی از graph کلاینت در نظر گرفته می‌شوند و معمولاً روی کلاینت اجرا می‌شوند. این رفتار در مستندات رسمی Next.js ذکر شده است. Next.js

قانون کلی (پیشفرض): فایل‌ها بدون "use client" = Server Component. با "use client" فایل به boundary client تبدیل می‌شود و «تمام» imports/فرزندانش جزو bundle کلاینت در نظر گرفته می‌شوند. این جمله رسمی است. Next.js

اما امکان interleaving (ترکیب) وجود دارد: Next.js اجازه می‌دهد یک Server Component که روی سرور رندر شده به‌عنوان prop یا JSX از سرور به یک Client Component منتقل شود (یعنی سرور می‌تواند بخشی از UI را render کند و آن HTML / payload را به کلاینت بدهد). این الگو (passing Server Components as props/children) یک مورد مستقل است و تفاوت مهمی با «import مستقیم Server Component داخل یک فایل که use client دارد» دارد. Next.js

نتیجهٔ عملی: اگر تو مستقیماً داخل یک فایل "use client" یک Server Component را import و render کنی، آن Server Component عملاً در محدودهٔ client قرار می‌گیرد (یعنی SSR واقعی را از دست می‌دهد). اما اگر سرور (یک Server Component یا صفحهٔ سرور) قبل از آن ServerChild را render کند و آن JSX را به Client Component بفرستد، آنگاه محتوای آن ServerChild در HTML اولیه خواهد بود. (این همان تفاوت «import مستقیم» vs «render on server and pass as prop» است). Next.js

تست عملی — سه سناریو با کد و خروجی مورد انتظار

هر سناریو را در یک پروژه‌ی سادهٔ app/ قرار بده و با next dev اجرا کن. بعد سرور (ترمینال) و DevTools مرورگر را نگاه کن.

سناریو A — Client layout که مستقیم import می‌کند (انتظار: fetch در کلاینت)

app/layout.jsx

"use client";
import ServerChild from "./components/ServerChild";

export default function Layout({ children }) {
  return (
    <div>
      <h1>Client Layout</h1>
      <ServerChild />
      {children}
    </div>
  );
}

app/components/ServerChild.jsx

export default async function ServerChild() {
  console.log("ServerChild: server render? ", new Date().toISOString());
  const res = await fetch("https://httpbin.org/get?src=serverchild");
  const data = await res.json();
  return <div id="server-child">server data url: {data.url}</div>;
}

انتظار/چک کن:

  • آیا console.log در ترمینال (سرور) چاپ می‌شود؟ احتمالاً خیر (چون فایل layout "use client" هست و imports جزء client bundle می‌شوند).
  • آیا در DevTools → Network برای httpbin.org ریکوئست می‌بینی؟ بله — یعنی fetch از مرورگر زده شده (Client-side).
  • View Source (صفحهٔ اصلی) غالباً محتوای fetch شده را ندارد (یا placeholder دارد)، چون fetch روی کلاینت اتفاق افتاده.
    تأیید مستند: "Once a file is marked with 'use client', all its imports and child components are considered part of the client bundle." Next.js

سناریو B — Server layout که ServerChild را import می‌کند (انتظار: SSR واقعی)

app/layout.jsx (بدون "use client")

import ServerChild from "./components/ServerChild";

export default function Layout({ children }) {
  return (
    <div>
      <h1>Server Layout</h1>
      <ServerChild />
      {children}
    </div>
  );
}

app/components/ServerChild.jsx همان کد بالا.

انتظار/چک کن:

  • console.log در ترمینال چاپ می‌شود → نشان‌دهندهٔ render روی سرور.
  • در DevTools → Network هیچ درخواست برای آن httpbin.org از مرورگر دیده نمی‌شود (زیرا سرور خودش fetch را انجام داده).
  • View Source شامل محتوای رندر شده (HTML با داده‌ها) است — یعنی SEO-friendly.
    مستند: Server Components on server rendered into the RSC payload / HTML. Next.js

سناریو C — می‌خواهی client layout داشته باشی ولی یک بخش از UI را سرور رندر شده دریافت کنی

الگوی صحیح: سرور یک Server Component را render کند و آن را به عنوان prop/child به Client Component بدهد. مثال:

app/page.jsx (Server page)

import ClientWrapper from "./layout";
import ServerChild from "./components/ServerChild";

export default function Page() {
  return <ClientWrapper serverChild={<ServerChild />} />;
}

app/layout.jsx (Client)

"use client";
export default function ClientWrapper({ serverChild, children }) {
  return (
    <div>
      <h1>Client Wrapper</h1>
      <div id="slot">{serverChild}</div> {/* serverChild already rendered on server */}
      {children}
    </div>
  );
}

انتظار/چک کن:

  • ServerChild در این حالت روی سرور اجرا شده (چون page.jsx سرور است و serverChild را ساخته) و HTML محتوای آن به عنوان prop به client داده می‌شود.
  • در View Source محتوای serverChild وجود دارد و در Network ریکوئستِ fetch را در مرورگر نمی‌بینی.
    مستند: “You can pass Server Components as a prop to a Client Component…” Next.js

نکات تکمیلی و منابع رسمی

  • RSC Payload و نحوهٔ prerender: Server Components تبدیل به RSC Payload می‌شوند و payload شامل نتیجهٔ رندر و placeholderها و props است. این payload توسط کلاینت برای بازسازی tree استفاده می‌شود. Next.js
  • next/dynamic با { ssr: false } برای غیر فعال کردن SSR فقط در Client Components معنا دارد — و ssr: false در یک Server Component مجاز نیست. (اگر از ssr:false در Server Component استفاده کنی، خطا می‌دهد). Next.js+1
  • اگر layout شما Client Component باشه ("use client" نوشته شده باشه)، اون layout خودش دیگه روی سرور اجرا نمیشه و کلاینتیه.
  • اما فرزندانش (مثلاً page یا حتی سرور کامپوننت‌های دیگه) همچنان می‌تونن Server Component باقی بمونن.

📌 یعنی:

  • layout = Client → روی کلاینت رندر میشه.
  • page (child) = Server → داده‌هاش روی سرور fetch میشن و به صورت SSR HTML به مرورگر ارسال میشن.

تنها چیزی که تغییر می‌کنه اینه که خروجی سرور کامپوننت باید به عنوان serialized RSC payload به کلاینت فرستاده بشه تا داخل اون layout کلاینتی render بشه. ولی همچنان fetch روی سرور انجام میشه و توی Network هم request data نمی‌بینی، فقط response HTML یا stream میاد.

layout می‌تونه client باشه، ولی page همچنان server-side باقی می‌مونه.

Accept Cookies
Accept Cookies
[your-shortcode]