پشت صحنه React Server Components؛ از اجرای سرور تا تعامل در مرورگر
وقتی یک صفحه React باز میشود، کدام بخش روی سرور اجرا میشود و چه چیزی به مرورگر میرسد؟ در این راهنمای معماری، مسیر اجرای React Server Components، مرز Server و Client، نقش RSC Payload، انتقال داده، Streaming و طراحی یک صفحه واقعی در Next.j
تصور کن کاربر وارد صفحه جزئیات یک محصول میشود. عنوان، تصویر، قیمت و توضیحات محصول باید نمایش داده شوند؛ موجودی باید از دیتابیس خوانده شود؛ دکمه افزودن به سبد خرید باید کار کند و فیلترهای صفحه نیز باید به تعامل کاربر پاسخ دهند.
یک سؤال معماری مهم وجود دارد:
آیا واقعاً لازم است تمام این قابلیتها در مرورگر اجرا شوند؟
React Server Components یا RSC پاسخی متفاوت به این سؤال میدهد. بهجای اینکه کل رابط کاربری را به یک محیط اجرایی وابسته کنیم، میتوانیم مسئولیتها را بین سرور و مرورگر تقسیم کنیم.
اما این تقسیم مسئولیت دقیقاً چگونه اتفاق میافتد؟ چه چیزی در سرور اجرا میشود؟ چه چیزی به مرورگر میرسد؟ و React چگونه این دو محیط را به یک رابط کاربری واحد تبدیل میکند؟
برای پاسخ، مسیر یک درخواست واقعی را دنبال میکنیم.
مرورگر به قابلیتهایی مثل window، localStorage، DOM، Event Handlerها و تعامل مستقیم کاربر دسترسی دارد.
سرور میتواند به دیتابیس، فایلهای سیستم، متغیرهای محیطی محرمانه و سرویسهای داخلی دسترسی داشته باشد.
مشکل زمانی ایجاد میشود که تلاش کنیم تمام مسئولیتها را در یک محیط قرار دهیم.
برای مثال، اگر یک صفحه فقط اطلاعات محصول را نمایش میدهد، چرا باید کد اتصال به دیتابیس یا کتابخانهای که فقط برای پردازش داده در سرور استفاده میشود، به مرورگر ارسال شود؟
از طرف دیگر، اگر کاربر روی دکمه افزودن به سبد خرید کلیک میکند، این تعامل به منطق سمت کلاینت نیاز دارد.
RSC این دو محیط را به دو بخش قابل ترکیب تقسیم میکند:
Server Components: برای اجرای منطق و رندر در محیط سرور.
Client Components: برای قابلیتهایی که به محیط مرورگر و تعامل کاربر نیاز دارند.
این تقسیمبندی را میتوان یک Network Boundary در درخت کامپوننتها در نظر گرفت؛ مرزی که مشخص میکند کدام بخش از برنامه در کدام محیط اجرا شود.
توسعهدهنده فرانتاند با بیش از 3 سال تجربه در ساخت وبسایتها و اپلیکیشنهای وب مدرن و واکنشگرا، با تمرکز بر React، Next.js، بهینهسازی عملکرد و تجربه کاربری.
Server Component نوعی کامپوننت React است که پیش از ارسال خروجی به Client، در محیط جداگانهای اجرا میشود.
این محیط میتواند:
هنگام Build روی سیستم CI باشد.
هنگام هر درخواست روی Web Server اجرا شود.
بنابراین Server Component الزاماً به معنی اجرای مجدد کامپوننت در هر درخواست نیست. زمان اجرا به معماری Framework و نحوه استفاده از دادهها بستگی دارد.
مثال:
import { db } from "@/lib/db";
type Props = {
productId: string;
};
export default async function ProductInformation({
productId,
}: Props) {
const product = await db.product.findUnique({
where: {
id: productId,
},
select: {
name: true,
description: true,
price: true,
},
});
if (!product) {
return <p>Product not found.</p>;
}
return (
<section>
<h1>{product.name}</h1>
<p>{product.description}</p>
<strong>${product.price}</strong>
</section>
);
}
چند نکته در این مثال وجود دارد:
کامپوننت میتواند async باشد.
اطلاعات مستقیماً از دیتابیس دریافت میشوند.
نیازی به useEffect برای دریافت اولیه اطلاعات نیست.
کد اتصال به دیتابیس بخشی از Client Bundle نخواهد بود.
نکته مهم: RSC بهخودیخود Database، ORM یا Backend ایجاد نمیکند. فقط امکان اجرای کامپوننت در محیط سرور را فراهم میکند؛ زیرساخت دسترسی به داده را باید خودتان فراهم کنید.
در Next.js App Router، بارگذاری اولیه صفحه از چند مرحله تشکیل میشود.
User Request
|
v
Next.js Server
|
v
Render Server Components
|
v
Generate RSC Payload
|
v
Pre-render HTML
|
v
Browser Receives Response
|
+------> Display HTML
|
+------> Reconcile RSC Tree
|
+------> Load Client JavaScript
|
v
Hydration
|
v
Interactive UI
این فرآیند را مرحلهبهمرحله بررسی کنیم.
مرحله اول: اجرای Server Components
سرور کامپوننتهایی را که در بخش Server قرار دارند اجرا میکند و دادههای موردنیاز را دریافت میکند.
مرحله دوم: تولید RSC Payload
React اطلاعات مربوط به درخت رندرشده و محل قرارگیری Client Components را آماده میکند.
مرحله سوم: تولید HTML اولیه
در Next.js، HTML اولیه از ترکیب خروجی Server Components و پیشرندر Client Components تولید میشود.
مرحله چهارم: نمایش صفحه
مرورگر میتواند HTML را نمایش دهد؛ حتی پیش از اینکه تعاملات کلاینت آماده باشند.
مرحله پنجم: Hydration
JavaScript مربوط به Client Components بارگذاری و اجرا میشود و React Event Handlerها را به DOM متصل میکند.
در نتیجه صفحه از یک خروجی اولیه قابل مشاهده به یک رابط کاربری تعاملی تبدیل میشود.
نکته مهم: Client Componentها در بارگذاری اولیه Next.js میتوانند برای تولید HTML روی سرور نیز رندر شوند؛ بنابراین Client Component به معنی «فقط اجرا در مرورگر» نیست.
در این ساختار، بخشهای مستقل میتوانند با زمانبندی متفاوت آماده شوند.
البته برای استفاده مؤثر از Streaming باید به محل قرارگیری Suspense، وابستگی دادهها و قابلیتهای Framework توجه کرد. اضافه کردن Suspense بهتنهایی تضمینکننده بهبود عملکرد نیست.
در پروژههای React ممکن است با این دو عبارت روبهرو شوید:
"use client";
و:
"use server";
این دو Directive کار یکسانی انجام نمیدهند.
"use client" مرز Client Module Graph را مشخص میکند.
"use server" برای تعریف Server Function استفاده میشود؛ تابعی که از Client قابل فراخوانی است اما در سرور اجرا میشود.
برای مثال:
"use server";
import { db } from "@/lib/db";
export async function addToCart(productId: string) {
// Authenticate the user.
// Validate the product.
// Check permissions.
// Update the database.
await db.cartItem.create({
data: {
productId,
},
});
}
این تابع میتواند از یک Client Component فراخوانی شود.
اما نکته امنیتی مهمی وجود دارد: Server Function را نباید یک تابع داخلی و غیرقابلدسترسی تصور کرد. درخواستهای ورودی آن باید مانند ورودی یک Endpoint سمت سرور اعتبارسنجی و مجوزسنجی شوند.
همچنین "use server" یک Server Component ایجاد نمیکند.
import { getProduct } from "@/lib/products";
import ProductDetails from "./components/product-details";
import QuantitySelector from "./components/quantity-selector";
import AddToCartButton from "./components/add-to-cart-button";
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const product = await getProduct(id);
return (
<main>
<ProductDetails product={product} />
<QuantitySelector />
<AddToCartButton productId={product.id} />
</main>
);
}
در این مثال:
صفحه، Server Component است.
اطلاعات محصول در سرور دریافت میشود.
انتخاب تعداد، Client Component است.
افزودن به سبد خرید، Client Component است.
داده لازم برای تعامل بهصورت محدود به کلاینت منتقل میشود.
برای اطلاعات بیشتر درباره جزئیات پیادهسازی و رفتار نسخههای مختلف Next.js، مستندات همان نسخه را بررسی کنید؛ بهخصوص رفتار Cache و Data Fetching ممکن است با نسخه Framework تغییر کند.
React Server Components صرفاً یک روش جدید برای نوشتن کامپوننت نیستند؛ آنها روی معماری، Data Flow، مرزبندی کد و نحوه ارسال JavaScript به مرورگر تأثیر میگذارند.
برای استفاده درست از RSC باید بهجای پرسیدن اینکه «این کامپوننت Server است یا Client؟»، سؤال دقیقتری بپرسیم:
این بخش از برنامه به کدام محیط نیاز دارد و چه چیزی واقعاً باید از مرز شبکه عبور کند؟
این تغییر نگاه، پایه طراحی معماری مناسب در پروژههای مدرن React است.