
React · Next.js · معماری
React Server Components در سال ۲۰۲۶؛ هر توسعهدهنده فرانتاند باید چه بداند؟
۱۴۰۵ مهر · 10 min دقیقه مطالعه
React Server Components یا RSC یکی از مهمترین ایدههای معماری در React مدرن است؛ اما در عین حال یکی از مفاهیمی است که بیشتر از همه بد برداشت میشود. RSC صرفاً «SSR با یک اسم جدید» نیست و به این معنی هم نیست که باید همه کامپوننتها را به سرور منتقل کنیم.
تغییر اصلی این است که کامپوننت کجا اجرا میشود، به چه دادههایی دسترسی دارد و مرورگر باید چه مقدار JavaScript دانلود و اجرا کند. در اپلیکیشنهای Next.js App Router، شناخت این مرز کمک میکند رابطهایی سریعتر، قابلفهمتر و امنتر طراحی کنیم.
۱. Server Components با SSR یکی نیستند
رندر سمت سرور یا SSR یک روش رندر است: سرور برای یک درخواست HTML تولید میکند. اما React Server Components امکان دیگری اضافه میکنند: کامپوننتها میتوانند در محیط سرور اجرا شوند و خروجیای با فرمت React Server Component Payload تولید کنند. این payload خروجی رندرشده کامپوننتهای سرور و ارجاع به Client Componentهایی را که لازماند در بر میگیرد.
این دو مفهوم میتوانند کنار هم کار کنند. در Next.js، کامپوننتهای سرور میتوانند در تولید خروجی اولیه صفحه نقش داشته باشند و Client Componentها در مرورگر hydrate شوند تا تعاملی شوند. در مسیریابیهای بعدی نیز فریمورک میتواند با استفاده از RSC payload رابط را بهروزرسانی کند، بدون اینکه کل صفحه مانند یک بارگذاری سنتی از نو بارگذاری شود.
- SSR: روشی برای تولید HTML در سرور.
- Server Components: مدلی برای اجرای کامپوننتهای واجد شرایط در سرور یا زمان build.
- Client Components: کامپوننتهایی که ماژولهای JavaScript آنها در گراف وابستگی کلاینت قرار میگیرند تا تعامل مرورگر را پشتیبانی کنند.
۲. مدل ذهنی درست در Next.js App Router
در App Router، صفحهها و layoutها بهصورت پیشفرض Server Component هستند. با قرار دادن 'use client' در ابتدای یک ماژول، مرز Client را مشخص میکنیم. این دستور فقط یک برچسب برای همان کامپوننت نیست؛ مرزی در گراف ماژولها ایجاد میکند و importهای وابسته به آن ماژول را وارد باندل سمت کلاینت میکند.
// app/products/page.jsx — Server Component
import AddToCartButton from './add-to-cart-button';
import { getProducts } from '@/lib/products';
export default async function ProductsPage() {
const products = await getProducts();
return (
<main>
<h1>Products</h1>
<ul>
{products.map((product) => (
<li key={product.id}>
<h2>{product.name}</h2>
<p>{product.price}</p>
<AddToCartButton productId={product.id} />
</li>
))}
</ul>
</main>
);
}
// app/products/add-to-cart-button.jsx — Client Component
'use client';
import { useState } from 'react';
export default function AddToCartButton({ productId }) {
const [added, setAdded] = useState(false);
function handleClick() {
// در محصول واقعی، endpoint یا Server Function را امن فراخوانی کنید.
setAdded(true);
}
return (
<button onClick={handleClick} data-product-id={productId}>
{added ? 'Added' : 'Add to cart'}
</button>
);
}
صفحه داده را در محیط سرور میگیرد و دکمه کوچک، تعامل محلی را مدیریت میکند. این یک پیشفرض مفید است: صفحه و UI دادهمحور را در سرور نگه دارید و فقط بخش تعاملی را به کلاینت منتقل کنید.
۳. چه زمانی Server Component انتخاب مناسبی است؟
Server Component معمولاً زمانی انتخاب خوبی است که کامپوننت نیاز داشته باشد:
- داده را از دیتابیس یا سرویس داخلی نزدیک به منبع دریافت کند.
- از credentialهای سمت سرور استفاده کند، بدون اینکه آنها را در معرض مرورگر قرار دهد.
- رابطی عمدتاً محتوایی یا نمایشی تولید کند.
- مقدار JavaScript ارسالی به مرورگر را کاهش دهد.
- بخشهای کندتر را stream کند تا محتوای مهم زودتر نمایش داده شود.
با این حال، Server Component همیشه بهطور خودکار سریعتر نیست. کوئری کند دیتابیس، درخواست غیرقابلکش یا زنجیرهای از awaitهای متوالی همچنان میتواند کاربر را منتظر نگه دارد. انتقال کد به سرور محیط اجرا را تغییر میدهد؛ اما latency شبکه یا دسترسی غیربهینه به داده را حذف نمیکند.
۴. چه زمانی به Client Component نیاز داریم؟
زمانی از Client Component استفاده کنید که رابط به قابلیتهای سمت مرورگر نیاز دارد، از جمله:
useState، event handlerها یا effectهایی مانندuseEffect.- APIهای مرورگر مانند
window،localStorageیا موقعیت جغرافیایی. - کتابخانههای تعاملی که به DOM وابستهاند.
- Context Providerها یا هوکهای سفارشی متکی به state سمت کلاینت.
یک اشتباه رایج این است که فقط بهخاطر نیاز یک عنصر کوچک به event handler، کل صفحه یا layout را با 'use client' مشخص کنیم. این کار میتواند ماژولهای بیشتری را وارد باندل کلاینت کند. بهتر است بخش تعاملی را در کامپوننت کوچکتری جدا کنیم و UI اطراف آن را در سرور نگه داریم.
۵. مرز Server و Client به importها مربوط است، نه فقط JSX
نکته مهم این است که Server Component میتواند یک Client Component را رندر کند؛ اما Client Component نباید مستقیماً یک ماژول Server Component را import کند و انتظار داشته باشد آن ماژول در سرور اجرا شود. در عوض، درخت کامپوننتها را از سمت Server Component ترکیب کنید و در صورت نیاز محتوای رندرشده سمت سرور را از طریق slotهایی مانند children به Client Component بدهید.
همچنین propهایی که از Server Component به Client Component میفرستید باید قابلسریالسازی باشند. آبجکتهای دلخواه سرور، اتصال دیتابیس یا تابعهایی را که نمیتوانند از این مرز عبور کنند ارسال نکنید. فقط دادهای را منتقل کنید که کامپوننت تعاملی واقعاً به آن نیاز دارد.
۶. دریافت داده، Streaming و وضعیتهای Loading
Server Components اجازه میدهند هنگام رندر از async/await استفاده کنید و حتی مستقیماً به لایه داده سمت سرور دسترسی داشته باشید. با این حال، ساختار دریافت داده همچنان اهمیت دارد.
- از Waterfall ناخواسته جلوگیری کنید: اگر دو درخواست مستقل دارید، در صورت امکان آنها را با
Promise.allموازی شروع کنید. - از Suspense هدفمند استفاده کنید: بخشهای کند و کماولویت را پشت مرز loading قرار دهید تا باقی صفحه زودتر نمایش داده شود.
- Loading مفید طراحی کنید: Skeleton باید شکل محتوای در حال بارگذاری را نشان دهد، نه اینکه صرفاً بینهایت انیمیشن داشته باشد.
- خطاها را مدیریت کنید: مشخص کنید اگر درخواست شکست خورد یا سرویس پاییندستی در دسترس نبود، چه اتفاقی میافتد.
Streaming میتواند تجربه ادراکی سرعت را بهتر کند، چون سرور بخشهای آماده را میفرستد، در حالی که بخشهای کندتر هنوز در حال آمادهشدن هستند. این قابلیت لزوماً عملیات زیرین را زودتر تمام نمیکند؛ بلکه میتواند صفحه را زودتر قابلاستفاده کند.
۷. Caching تصمیم جداگانهای است
فرض نکنید «Server Component» یعنی «داده کش میشود». محل اجرا و سیاست کش دو موضوع جدا هستند. در Next.js رفتار کش به نسخه فریمورک، تنظیمات route، الگوی دسترسی به داده و قابلیتهای فعالشده بستگی دارد.
برای هر منبع داده، به این پرسشها پاسخ دهید:
- آیا نتیجه بین کاربران قابل اشتراک است یا مختص یک کاربر است؟
- داده باید چقدر تازه باشد؟
- چه رویدادی باعث invalidate یا revalidate شدن کش میشود؟
- آیا ممکن است کش، اطلاعات یک کاربر را به کاربر دیگری نشان دهد؟
توضیحات عمومی محصول ممکن است قابلکش باشند؛ اما قیمت بیمه شخصیسازیشده، اطلاعات حساب یا داشبورد خصوصی به سیاست بسیار دقیقتری نیاز دارد. مدل کش مستندات نسخه فعلی Next.js خود را بررسی کنید و مثال قدیمی را با این فرض کپی نکنید که رفتار پیشفرض تغییر نکرده است.
۸. امنیت: کد سمت سرور خودبهخود امن نیست
نگهداشتن دسترسی دیتابیس و secretها در سرور میتواند سطح افشا را کاهش دهد، اما جایگزین authorization نیست. هر Server Function، route handler یا عملیات سمت سرور که داده را تغییر میدهد باید ورودی را اعتبارسنجی کند و بررسی کند کاربر فعلی اجازه انجام آن عملیات را دارد یا نه.
- مخفی بودن یک دکمه را مکانیزم authorization در نظر نگیرید.
- ورودی را در مرز سرور اعتبارسنجی کنید.
- برای هر عملیات حساس، مالکیت و سطح دسترسی را بررسی کنید.
- فقط فیلدهایی را به کلاینت برگردانید که نیاز دارد.
- secretها یا دادههای داخلی حساس را در propها قرار ندهید.
مرز Server/Client را یک مرز معماری بدانید، نه جایگزینی برای امنیت اپلیکیشن.
۹. اشتباهات رایجی که باید از آنها دوری کرد
- استفاده از
'use client'در همهجا: این کار میتواند سطح JavaScript سمت کلاینت را افزایش دهد. - یکی دانستن RSC و SSR: این دو مسئلههای مرتبط اما متفاوتی را حل میکنند.
- فرض سریع بودن کد صرفاً به دلیل اجرا در سرور: کوئری کند و درخواستهای متوالی همچنان کند هستند.
- ارسال داده بیش از حد به کلاینت: فقط داده ضروری را serialize کنید.
- ترکیب وابستگیهای سرور و مرورگر: کد وابسته به هر محیط را در گراف ماژول مناسب نگه دارید.
- نادیده گرفتن محدوده کش: پاسخهای مختص کاربر نباید اشتباهاً بین کاربران به اشتراک گذاشته شوند.
- استفاده از Server Function بدون بررسی مجوز: اجرای سمت سرور بهخودیخود عملیات را قابلاعتماد نمیکند.
۱۰. چکلیست عملی برای تصمیمگیری
هنگام اضافه کردن یک کامپوننت به پروژه Next.js App Router، این سؤالها را بپرسید:
- آیا به state، event handler، effect یا API مرورگر نیاز دارد؟ اگر بله، احتمالاً Client Component مناسب است.
- آیا عمدتاً داده دریافت و نمایش میدهد؟ با Server Component شروع کنید.
- آیا میتوان بخش تعاملی را جدا کرد، بهجای اینکه کل صفحه را client-side کنیم؟
- آیا propهای عبوری از مرز، قابلسریالسازی و حداقلی هستند؟
- آیا درخواستهای مستقل موازی شدهاند و بخشهای کند loading state معنادار دارند؟
- آیا سیاست کش با نیاز به تازگی داده و حریم خصوصی سازگار است؟
- آیا mutationهای سمت سرور اعتبارسنجی و authorization دارند؟
جمعبندی
React Server Components درباره انتخاب «سرور بهجای کلاینت» نیستند؛ درباره انتخاب آگاهانه محل اجرا هستند. اپلیکیشن خوب از سرور برای دسترسی به داده و رندر در جاهایی که منطقی است استفاده میکند و تعاملهایی را که واقعاً به مرورگر نیاز دارند به کلاینت میسپارد.
برای توسعهدهنده فرانتاند در سال ۲۰۲۶، مهارت ارزشمند حفظ کردن چند directive نیست. مهارت اصلی درک مرز اجرا، جریان داده، رفتار loading، کش و پیامدهای امنیتی آنهاست؛ و توانایی توضیح اینکه چرا هر کامپوننت باید در آن سمت از مرز قرار بگیرد.
مطالعه بیشتر: مستندات React درباره Server Components · مستندات Next.js درباره Server و Client Components · مستندات Next.js درباره دریافت داده