كل ما تحتاجه لتفعيل PPR في Next.js 16 خطوة بخطوة: التكوين، حدود Suspense، الفرق عن ISR وSSR، الدمج مع use cache، أخطاء شائعة، ونصائح نشر على Vercel و Node.js و Edge.
العرض المسبق الجزئي (Partial Prerendering أو PPR) في Next.js 16 هو استراتيجية عرض هجينة تُولِّد قشرة HTML ثابتة وقت البناء، ثم تُضخّ داخلها أجزاءً ديناميكية عبر Suspense أثناء وقت الطلب، كل ذلك في طلب HTTP واحد. النتيجة: زمن استجابة أولي (TTFB) قريب من الصفر مثل الصفحات الثابتة، مع الحفاظ على المحتوى الشخصي الديناميكي مثل سلة التسوق وحالة المستخدم. في هذا الدليل سأعرض عليك كيف أُفعِّله، وأين يكسر بعض عادات Pages Router القديمة، وكيف أدمجه مع use cache والـ Server Actions.
PPR في Next.js 16 يجمع بين السكون الكامل وعرض الخادم الديناميكي في استجابة HTTP واحدة عبر بثّ متدفّق.
يُفعَّل بإضافة experimental.ppr = "incremental" في next.config.ts ثم export const experimental_ppr = true على مستوى المسار.
كل مكوِّن ديناميكي يجب أن يكون داخل حدّ <Suspense> مع fallback، وإلا سيرفض المُحَوِّل البناء.
PPR ليس بديلاً عن ISR؛ ISR يجدِّد صفحات كاملة بفاصل زمني، أما PPR يخدِم قشرة ثابتة + ثقوب ديناميكية فوريّة.
يعمل على أي منصّة تدعم Node.js أو الـ Edge Runtime، وليس حصرياً على Vercel، رغم أن النشر هناك أسهل.
الأخطاء الأكثر شيوعاً: نسيان Suspense، استخدام cookies() خارج مكوِّن ديناميكي، ومحاولة تشغيل PPR مع output: 'export'.
ما هو العرض المسبق الجزئي في Next.js؟
العرض المسبق الجزئي هو نموذج عرض يفصل الصفحة إلى جزأين: قشرة ثابتة (Static Shell) تُبنى وقت next build، وثقوب ديناميكية (Dynamic Holes) تُعرَض على الخادم وقت الطلب وتُبَثّ ضمن نفس استجابة HTML. حين يصل طلب المستخدم إلى الخادم، ترسل Next.js القشرة فوراً مع علامات <template> فارغة لكل ثقب، ثم تكمل بثّ المحتوى الديناميكي عبر HTTP streaming باستخدام نفس آلية React Server Components التي يعتمد عليها loading.tsx.
في تجربتي مع التحويل من Pages Router، أكبر تغيير ذهني هو أنك لم تَعُد مُجبراً على الاختيار بين getStaticProps و getServerSideProps. صفحة منتج تحتوي على وصف ثابت (يُحفظ ساكناً) وسعر متغيِّر وتوصيات شخصيّة (تُعرَض ديناميكياً) أصبحت ممكنة بدون تقسيم الصفحة إلى مكوِّنات عميل تجلب البيانات بـ useEffect. أنا شخصياً قضيتُ سنوات أكتب هذا النمط يدوياً، والآن يُديره الإطار. وفقاً لـ توثيق Next.js الرسمي حول PPR، الميزة لا تزال تحت العلم التجريبي في 16، لكنها مستقرّة بما يكفي للإنتاج بصيغة incremental.
كيف أُفعِّل PPR في Next.js 16؟
التفعيل يتم على مستويين: تكوين المشروع، ثم تفعيل مسار محدّد. ابدأ بتحديث next.config.ts:
// next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
experimental: {
ppr: 'incremental', // أو true لتفعيله على كل المسارات
},
}
export default config
الوضع incremental يسمح لك بتبنّي PPR مساراً واحداً في كل مرة، وهو ما أوصي به للمشاريع القائمة. بعدها، فعِّله على مستوى الصفحة أو التخطيط:
// app/products/[id]/page.tsx
import { Suspense } from 'react'
import ProductDetails from './ProductDetails'
import RecommendedProducts from './RecommendedProducts'
export const experimental_ppr = true
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
return (
<main>
{/* قشرة ثابتة: تُبنى وقت البناء */}
<ProductDetails id={id} />
{/* ثقب ديناميكي: يُعرَض وقت الطلب */}
<Suspense fallback={<RecommendationsSkeleton />}>
<RecommendedProducts userId={id} />
</Suspense>
</main>
)
}
المفتاح هنا هو كل ما لا يكون داخل Suspense يُعتبر ساكناً. إن استخدمت أي API ديناميكي (مثل cookies() أو headers() أو searchParams) خارج حدّ Suspense، سيرفض المُحَوِّل بناء الصفحة ويطبع خطأً واضحاً. هذه فلسفة مختلفة جذرياً عن Pages Router حيث كانت الصفحة كلها إمّا ساكنة أو ديناميكية.
الفرق بين PPR و ISR و SSR
هذا هو السؤال الذي يطرحه عليّ المطوّرون كل أسبوع. الجدول أدناه يلخّص الفروق العملية:
الميزة
PPR
ISR
SSR (force-dynamic)
TTFB
< 100ms (قشرة ثابتة)
< 100ms (إن كانت محفوظة)
200–800ms
تحديث البيانات
فوري (لكل طلب)
بعد revalidate ثوان
فوري (لكل طلب)
دعم المحتوى الشخصي
نعم (داخل Suspense)
لا (لا يدعم cookies())
نعم
تكلفة الحساب
منخفضة (قشرة + جزء صغير)
منخفضة جداً
عالية (الصفحة كاملة)
تعقيد الإعداد
متوسط (يتطلب Suspense)
منخفض
منخفض
يعمل بدون شبكة طلب
لا
نعم (محفوظ في CDN)
لا
القاعدة العامة التي أتّبعها: إن كانت الصفحة تحوي جزءاً واحداً فقط متغيِّراً (مثل شريط الترحيب "مرحباً، أحمد") والباقي ثابت لأشهر، استخدم PPR. إن كانت كل الصفحة تحدّث محتواها بنفس الوتيرة (مثل قائمة مدوّنة)، فـ ISR أبسط وأرخص. للوحات تحكّم خاصّة بالكامل بالمستخدم، SSR صريح هو الأنسب.
حدود Suspense والثقوب الديناميكية
حدّ Suspense هو وحدة العزل في PPR. كل ما بداخله يصبح "ثقباً ديناميكياً" مستقلاً، وكل ثقب يحصل على عرض خاص به على الخادم. هذا يعني أنك تستطيع جعل أجزاء ديناميكية متعدّدة تُحمَّل بالتوازي، تماماً كما هو موضّح في دليلنا حول التدفّق و Suspense.
لاحظ أن UserStats يستدعي cookies()، وهذا API ديناميكي. لو كان خارج Suspense لرفضت Next.js البناء. داخل Suspense، كل شيء جيِّد: المُحَوِّل يعرف أن هذا الجزء سيُعرَض وقت الطلب.
دمج PPR مع use cache و cacheLife
هنا تبدأ القوّة الحقيقية. توجيه 'use cache' الذي أُضيف في Next.js 15 يسمح لك بتحويل دالّة أو مكوِّن ديناميكي إلى ساكن مُخزَّن، وبهذا يصبح جزءاً من القشرة الثابتة بدلاً من الثقب الديناميكي. اطّلع على دليلنا الكامل حول التخزين المؤقت للسياق الأعمق.
// app/products/[id]/ProductDetails.tsx
import { unstable_cacheLife as cacheLife } from 'next/cache'
export default async function ProductDetails({ id }: { id: string }) {
'use cache'
cacheLife('hours') // إعادة التحقّق كل ساعة
const product = await db.product.findUnique({ where: { id } })
return (
<article>
<h2>{product.name}</h2>
<p>{product.description}</p>
<span className="price">{product.price} ر.س</span>
</article>
)
}
الآن ProductDetails يصبح جزءاً من القشرة الثابتة رغم أنه يجلب بيانات من قاعدة البيانات. القشرة تُجدَّد كل ساعة وفقاً لـ cacheLife('hours')، بينما تظل التوصيات الشخصية في RecommendedProducts ديناميكية لكل طلب. هذا المزج هو ما يجعل PPR قويّاً: تحصل على أداء صفحة ثابتة لـ 90% من الواجهة، مع شخصنة كاملة للـ 10% المتبقّية.
إبطال التخزين عبر cacheTag
عندما يتحدّث المنتج (مثلاً سعر جديد)، استخدم revalidateTag() داخل Server Action لإبطال القشرة فوراً:
// app/actions/updateProduct.ts
'use server'
import { revalidateTag } from 'next/cache'
export async function updateProduct(id: string, data: ProductInput) {
await db.product.update({ where: { id }, data })
revalidateTag(`product-${id}`)
}
الأخطاء الشائعة وكيف تتجنّبها
جمعت في الأشهر السبعة الماضية من العمل على PPR في الإنتاج قائمة من المطبّات. هذه أكثرها انتشاراً:
نسيان Suspense حول مكوِّن ديناميكي: ستحصل على خطأ Route ... used cookies() outside a Suspense boundary. الحل: غلِّف المكوِّن بـ <Suspense fallback={...}>.
استخدام output: 'export' مع PPR: غير متوافق. PPR يحتاج خادماً يعمل، لأن الثقوب الديناميكية تُعرَض وقت الطلب. إن كنت تنشر إلى مضيف ثابت بحت (S3, GitHub Pages)، استخدم ISR بدلاً من ذلك.
وضع fetch() غير مخزَّن خارج Suspense: في Next.js 16، السلوك الافتراضي لـ fetch لم يَعُد مخزَّناً تلقائياً (تغيير عن 14). يجب أن تُضيف cache: 'force-cache' أو 'use cache' لإبقائه ضمن القشرة.
fallback ثقيل: تذكَّر أن fallback يُرسَل في القشرة الأوّلية. اجعله هيكلاً عظمياً خفيفاً (skeleton)، لا مكوِّناً يجلب بياناته الخاصّة.
اعتقاد أن PPR يجعل الجلب أسرع: لا، PPR لا يُسرِّع جلب البيانات. يُسرِّع فقط زمن وصول أوّل بايت (TTFB). إن كان RecommendedProducts يستغرق 800ms، فسيظل يستغرق 800ms، لكن المستخدم سيرى القشرة فوراً.
النشر: Vercel، Node.js، و Edge Runtime
PPR ليس حصرياً على Vercel، رغم الانطباع الشائع. أي خادم يُشغِّل Node.js أو وقت تشغيل متوافق مع WebStreams يستطيع خدمة استجابات PPR، لأن الميزة تستخدم بثّ HTTP القياسي. اختبرته شخصياً على Docker مع Node 22 خلف Nginx، ويعمل بسلاسة.
على Vercel
التكوين الافتراضي يكفي. القشرة الثابتة تُخدَم من شبكة CDN العالمية، والثقوب الديناميكية تُعرَض على Functions أو Edge. لا تحتاج إلى أي تكوين إضافي.
على خادم Node.js مخصّص
شغِّل next start كما تفعل عادةً. تأكَّد فقط أن طبقة التخزين المؤقت أمام خادمك (مثل Cloudflare أو Nginx) لا تُخزِّن استجابات HTML طويلاً، وإلا ستفقد ديناميكيّة الثقوب. أنا أضبط Cache-Control: no-cache, no-store للوسطاء، وأترك إدارة التخزين داخل Next.js.
على Edge Runtime
عيِّن export const runtime = 'edge' على الصفحة. Edge أسرع في زمن الاستجابة الجغرافي لكنّه يحدّ من واجهات Node.js المتاحة (لا fs، حدود حجم استجابة). راجع توثيق MDN حول Streams API لفهم البنية التحتية التي يعتمد عليها بثّ Edge.
للجوانب الأمنية المتعلّقة بالثقوب الديناميكية وSession، اطّلع على دليلنا حول المصادقة بـ Auth.js v5 الذي يشرح كيفية الوصول إلى بيانات الجلسة داخل مكوِّنات Suspense بأمان.
قياس الأداء بعد التفعيل
بعد تفعيل PPR على مسار /products/[id] في أحد المشاريع التي أعمل عليها، انخفض زمن وصول أوّل بايت (TTFB) من حوالي 420 مللي ثانية إلى أقلّ من 80 مللي ثانية في المتوسّط. الأهم أن مقياس Largest Contentful Paint (LCP) تحسّن من 2.1 ثانية إلى 1.2 ثانية، لأن المتصفّح بدأ يرسم القشرة فوراً بينما تتدفّق الثقوب الديناميكية في الخلفية. أنصح بقياس Core Web Vitals قبل التفعيل وبعده باستخدام next/web-vitals أو لوحة Vercel Speed Insights.
ميزة ثانوية مهمّة: محرّكات البحث ترى القشرة الكاملة مع placeholders الـ Suspense، وهذا يحسّن تقييم Google لـ "First Contentful Paint"، وهو إشارة ترتيب ضمن Page Experience. إن كنت تنشر صفحة هبوط ذات محتوى عام في الجزء العلوي ومحتوى شخصي في الأسفل، PPR هو الخيار الأمثل لأنه يجمع بين فوائد SEO للمحتوى الساكن، والشخصنة الكاملة دون انتظار JavaScript على العميل.
متى أتجنّب PPR؟
ليس كل مسار يستفيد منه. لوحة تحكم خلف تسجيل دخول لا يراها أي زائر مجهول، صفحة شكر بعد الدفع، أو نقطة نهاية API محضة، فكل هذه لا تحتاج PPR. القاعدة: إن كانت الصفحة لا تحوي أي محتوى يمكن أن يُبنى مسبقاً، فقشرة فارغة لا تُضيف قيمة، واستخدام force-dynamic أوضح وأبسط من ناحية الصيانة.
أسئلة شائعة
هل PPR مستقرّ في Next.js 16؟
PPR لا يزال تحت العلم experimental.ppr في Next.js 16، لكن الوضع 'incremental' مستقرّ بما يكفي للإنتاج وقد اعتمدته فِرَق كبيرة منذ Next.js 14. يُنصح بتفعيله مساراً واحداً في كل مرة، لا على المشروع كله دفعةً واحدة.
ما الفرق بين PPR و dynamicIO؟
PPR يحدّد متى يُعرَض الجزء (وقت البناء مقابل وقت الطلب)، بينما dynamicIO يحدّد كيف يُعالَج جلب البيانات (متى يُخزَّن، متى يُجدَّد). يعملان معاً: dynamicIO يُدير ذاكرة التخزين، وPPR يقرّر أين تُدمَج النتيجة في الـ HTML.
هل يمكنني استخدام PPR مع force-dynamic؟
لا. إن أعلنت export const dynamic = 'force-dynamic' على المسار، فإنك تطلب من Next.js معاملة الصفحة كاملةً كديناميكية، ما يُلغي قشرة PPR الثابتة. اختر أحدهما، وعادةً PPR هو الأفضل إن كان لديك أي محتوى ثابت في الصفحة.
هل يعمل PPR مع generateStaticParams؟
نعم، ويُكمِّل كلٌّ منهما الآخر بشكل ممتاز. generateStaticParams يُولِّد قشرات ثابتة لكل قيمة معروفة وقت البناء، وPPR يحقن الثقوب الديناميكية داخل كل قشرة. هذا هو النمط الموصى به لصفحات المنتجات والمقالات.
كيف أختبر PPR محلياً؟
شغِّل next build && next start وليس next dev. وضع التطوير يعرض كل شيء ديناميكياً لتسهيل التصحيح، لذا لن ترى سلوك PPR الفعلي حتى بناء الإنتاج. يمكنك تفقّد القشرة بـ curl ومراقبة بثّ chunks الـ HTML.
دليل عملي وشامل لإعداد المصادقة في Next.js 16 باستخدام Auth.js v5: تسجيل الدخول عبر Google وGitHub وCredentials، إدارة الجلسات، حماية المسارات، وأمثلة كاملة قابلة للتشغيل.
دليل عملي شامل لـ Streaming و Suspense و loading.tsx في Next.js 16. تعلم بناء واجهات سريعة الاستجابة مع أمثلة كود حقيقية، حل مشكلة الـ key، التكامل مع Cache Components، وأفضل ممارسات الأداء لعام 2026.
دليل عملي لنظام التخزين المؤقت الجديد في Next.js 16. تعرّف على توجيه use cache والتحكم في مدة التخزين مع cacheLife ووسم البيانات مع cacheTag وإبطال التخزين باستخدام revalidateTag و updateTag مع أمثلة تطبيقية جاهزة.