Partial Prerendering یا PPR کمک میکند بخش قابل پیشساخت یک صفحه زودتر آماده شود و بخش وابسته به درخواست، جداگانه رندر شود. برای مثال، توضیح یک محصول بین بازدیدکنندگان مشترک است، اما پیام ورود و سبد خرید هر نفر فرق دارد. اگر تمام صفحه منتظر داده شخصی بماند، محتوای مشترک هم دیرتر آماده میشود. اگر داده شخصی را بیدقت در خروجی مشترک بگذارید، مسئله محرمانگی پیدا میکنید. طراحی مرز میان این دو بخش، اصل کار در PPR است.
این مطلب از Next.js 15 شروع شده بود و درصدهایی درباره کاهش زمان پاسخ و مصرف حافظه داشت که پشتوانه مشخصی نداشتند. آن اعداد حذف شدهاند. در مستندات نسخه 15، PPR قابلیت آزمایشی بود. راهنمای ارتقا به نسخه 16 توضیح میدهد که تنظیمات قدیمی PPR حذف شده و مسیر فعالسازی به Cache Components منتقل شده است. نسخه پروژه را پیش از کپی تنظیمات بررسی کنید؛ مثال قدیمی canary قرارداد نسخه جدید نیست.
صفحه را بر اساس نیاز داده تقسیم کنید
تقسیم صفحه فقط یک کار بصری نیست. دو کارت کنار هم ممکن است چرخه داده متفاوتی داشته باشند. معرفی فروشگاه بهندرت تغییر میکند؛ موجودی یک کالا ممکن است با هر خرید تغییر کند؛ نام مشتری به درخواست فعلی وابسته است. برای هر بخش بنویسید داده از کجا میآید، چه کسی اجازه دیدن آن را دارد و چه میزان کهنگی پذیرفتنی است. این یادداشت از تصمیم مبهم «همه چیز را cache کن» جلوگیری میکند.
در مثال فروشگاه، عنوان صفحه و توضیح روش ارسال را میتوان بدون شناخت بازدیدکننده آماده کرد. برای نمایش تعداد کالای سبد، باید نشست همان فرد را شناخت. لازم نیست توضیح ارسال در انتظار سبد باشد. در عین حال قیمت نهایی سفارش را نباید از یک متن عمومی صفحه نتیجه گرفت. محاسبه قابل اتکا هنگام ثبت سفارش در سرور انجام میشود، حتی اگر صفحه قیمت تقریبی را از خروجی cache شده نشان دهد.
گاهی محصول به PPR نیاز ندارد. یک صفحه تماس که هیچ بخش وابسته به درخواست ندارد با خروجی کاملاً static سادهتر است. در یک پنل داخلی که بیشتر اطلاعات آن شخصی است، سهم پوسته مشترک شاید کوچک باشد. اضافه کردن چندین مرز رندر بدون نیاز روشن، فهم صفحه و مدیریت خطا را دشوار میکند. از یک مسیر واقعی شروع کنید و سهم محتوای مشترک آن را مشخص کنید.
Suspense محل انتظار را تعیین میکند
Suspense برای زیرشاخهای که هنوز آماده نشده fallback نشان میدهد. در معماری PPR، fallback بخشی از پوسته اولیه است و محتوای آن زیرشاخه بعداً میرسد. قرار دادن مرز نزدیک به بخش وابسته به درخواست اجازه میدهد محتوای مستقل بیرون آن باقی بماند. توضیح مدل کنونی در راهنمای prerendering آمده است. Streaming را با اجرای تمام صفحه در مرورگر اشتباه نگیرید؛ تولید محتوا همچنان میتواند در سرور انجام شود.
Fallback باید جای محتوا و معنای انتظار را روشن کند. اگر قرار است یک جمله کوتاه درباره ورود کاربر نمایش داده شود، یک سطر وضعیت از Skeleton بزرگ مناسبتر است. اگر بخش فهرستی طولانی دارد، تغییر ناگهانی ارتفاع صفحه میتواند خواندن متن بالاتر را مختل کند. پیشبینی اندازه تقریبی، کمک میکند بارگذاری بخش تازه جابهجایی غیرضروری ایجاد نکند. جزئیات این تصمیم در مقاله طراحی Loading States و Skeleton Screens توضیح داده شده است.
مرز انتظار با مرز خطا یکسان نیست. اگر خواندن داده شکست بخورد، fallback بارگذاری نباید برای همیشه بماند. تجربه کاربر باید راهی برای فهم خطا و ادامه کار داشته باشد. بخش شخصی ناموفق نباید اطلاعات عمومی سالم صفحه را هم بیدلیل پنهان کند. در طراحی صفحه، وضعیت آماده، در حال انتظار و ناموفق را کنار هم تعریف کنید تا هر کدام متن و رفتار مشخصی داشته باشند.
Cache Components و طول عمر داده
در نسخه 16، فعالسازی cacheComponents مدل جدید را برای مسیرها فراهم میکند. use cache امکان cache کردن نتیجه تابع یا component را میدهد. ورودی تابع و برخی مقادیر بستهشده در کلید cache دخیلاند؛ هر دادهای هم برای این مرز قابل serializing نیست. مرجع use cache محدودیتها را توضیح میدهد. دستور را صرفاً بالای هر تابعی که کند است قرار ندهید؛ ابتدا مالک داده و شیوه تازهسازی را تعیین کنید.
طول عمر cache یک تصمیم محصولی است. راهنمای عمومی ارسال ممکن است مدتی ثابت بماند، اما تغییر شرایط فروش باید به موقع دیده شود. برای هر داده مشخص کنید تغییر از کدام رویداد میآید: ثبت ویرایش در CMS، تغییر موجودی یا پایان زمان اعتبار. اگر مسئول محتوا نداند انتشار تغییر چه زمانی در سایت دیده میشود، سیستم از نظر او غیرقابل اعتماد خواهد بود؛ حتی اگر زمان پاسخ سرور کوتاه باشد.
Cache مشترک و داده شخصی باید مرز روشن داشته باشند. تشخیص کاربر را در زیرشاخه وابسته به درخواست انجام دهید. اگر داده شخصی را به تابع cache شده میدهید، باید هویت، مجوز و کلید cache را با هم بررسی کنید. پنهان کردن یک دکمه در Client Component جای بررسی دسترسی هنگام خواندن داده را نمیگیرد. هیچ خروجی مشترکی نباید تنها به خاطر شباهت ظاهر، داده دو کاربر را یکی فرض کند.
نمونه یک صفحه با پیام وابسته به Cookie
نمونه زیر برای Next.js 16 و App Router نوشته شده است. پروژه را با create-next-app از خانواده 16 بسازید، TypeScript و App Router را انتخاب کنید و برای هماهنگی مسیرها گزینه src directory را غیرفعال کنید. فایل next.config.ts را با تنظیم زیر جایگزین کنید. اگر تنظیم دیگری دارید، مقدار cacheComponents را به همان فایل اضافه کنید و تنظیمهای لازم قبلی را حفظ کنید.
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;
فایل app/layout.tsx را با ساختار زیر نگه دارید. این Root Layout تگهای لازم صفحه و زبان فارسی را تعریف میکند. نمونه از فونت خارجی، فایل تصویر یا CSS جداگانه استفاده نمیکند و تنها styleهای محلی خود را دارد. برای اضافه کردن پوسته بصری به پروژه واقعی، همین ساختار را با Layout موجود ترکیب کنید و Root Layout دوم نسازید.
import type { ReactNode } from "react";
export default function RootLayout({ children }: { children: ReactNode }) {
return <html lang="fa" dir="rtl"><body style={{ margin: 0 }}>{children}</body></html>;
}
کد بعدی محتوای کامل app/page.tsx است. متن عمومی بدون خواندن Cookie ساخته میشود. زیرشاخه VisitorMessage داخل Suspense، Cookie به نام visitor را میخواند و فقط بین دو پیام آموزشی انتخاب میکند. این Cookie هویت معتبر یا مجوز دسترسی نیست و از آن برای نمایش نام واقعی یا داده حساب استفاده نشده است. برای شناخت کاربر واقعی باید نشست را در سرور اعتبارسنجی کنید.
import { Suspense } from "react";
import { cookies } from "next/headers";
async function VisitorMessage() {
const cookieStore = await cookies();
const returning = cookieStore.get("visitor")?.value === "returning";
return <p>{returning ? "خوش برگشتید." : "از آشنایی با شما خوشحالیم."}</p>;
}
export default function Page() {
return (
<main style={{ maxWidth: "48rem", margin: "2rem auto", padding: "1rem", fontFamily: "sans-serif", lineHeight: 1.8 }}>
<h1>راهنمای فروشگاه</h1>
<p>روش ارسال و توضیح خدمات برای همه بازدیدکنندگان مشترک است.</p>
<section aria-label="پیام بازدیدکننده" style={{ minHeight: "4rem", borderTop: "1px solid #bbb" }}>
<Suspense fallback={<p role="status">پیام بازدیدکننده آماده میشود.</p>}>
<VisitorMessage />
</Suspense>
</section>
</main>
);
}
پس از ذخیره فایلها، npm run dev را برای توسعه اجرا کنید. مسیر اصلی پروژه صفحه نمونه را نشان میدهد. برای آماده کردن خروجی استقرار از npm run build و سپس npm run start استفاده میشود. نمونه عمداً تأخیر ساختگی ندارد؛ ممکن است پیام وابسته به درخواست آنقدر زود آماده شود که fallback را نبینید. دیدن یا ندیدن یک وضعیت کوتاه، به تنهایی اثبات معماری یا عملکرد نیست.
در نمونه، cookies یک API ناهمزمان است و با await خوانده میشود. مرجع cookies رفتار خواندن و محدودیت تغییر Cookie را شرح میدهد. Server Component برای خواندن مناسب است؛ تغییر Cookie به محل مجاز مانند Server Action یا Route Handler نیاز دارد. پیام آموزشی این مثال هیچ Cookie تازهای تنظیم نمیکند و وظیفهاش فقط نشان دادن مرز داده درخواست است.
مهاجرت و استقرار را از ظاهر صفحه جدا کنید
اگر پروژه روی Next.js 15 است، فعال کردن یک option متعلق به نسخه 16 کافی نیست. ابتدا وابستگیها و راهنمای ارتقا را بررسی کنید. رفتار APIهای درخواست، تنظیمات cache و کدهایی که قبلاً بر یک پیشفرض تکیه داشتند باید مرور شوند. تغییر نسخه و بازطراحی تمام UI را همزمان انجام ندهید؛ در صورت تفاوت خروجی، پیدا کردن علت سختتر میشود. راهنمای مهاجرت به App Router به تفکیک این مرحلهها مربوط است.
محیط استقرار باید Streaming و رفتار cache مورد نیاز برنامه را پشتیبانی کند. Reverse Proxy یا تنظیمی که پاسخ را تا پایان کامل جمع میکند، برداشت شما از زمان رسیدن بخشهای صفحه را تغییر میدهد. در چند instance نیز باید بدانید cache و ابطال آن چه محدودهای دارد. راهنمای Self-Hosting مرجع تصمیمهای مربوط به میزبانی است؛ PPR را قابلیت انحصاری یک CDN فرض نکنید.
برای گزارش نتیجه، معیار را به تجربه کاربر وصل کنید. رسیدن پوسته، نمایش اطلاعات اصلی و آماده شدن عمل شخصی سه لحظه متفاوتاند. اگر عنوان سریع ظاهر شود ولی دکمه خرید هنوز داده لازم نداشته باشد، گزارش تنها TTFB تصویر کاملی نمیدهد. تصمیم بعدی باید از روی نیاز همان مسیر گرفته شود: چه چیزی زودتر قابل استفاده شد، کدام درخواست هنوز منتظر میماند و چه دادهای در زمان تغییر تازه میشود.
هنوز دیدگاهی ثبت نشده
اولین نفری باشید که نظر میدهد!