صراحةً، أكبر شيء حصل في Next.js منذ App Router هو هذا: التصيير المسبق الجزئي (Partial Prerendering) صار ميزة مستقرة رسمياً في Next.js 16. وأنا شخصياً جربته على مشروع متجر إلكتروني في الأسابيع الأخيرة، والفرق في الأداء كان… مذهلاً نوعاً ما. في هذا الدليل لعام 2026 رح نمشي خطوة بخطوة: كيف تفعّل PPR عبر cacheComponents، كيف تبني صفحات تجمع قشرة ثابتة فورية مع ثقوب ديناميكية تُبثّ تدريجياً، وكيف تتفادى الفخاخ الشائعة (وصدّقني، فيه فخاخ).
ما هو Partial Prerendering ولماذا يهم في 2026؟
الفكرة بسيطة فعلاً. بدل ما تختار بين صفحة ثابتة بالكامل (SSG) أو صفحة ديناميكية بالكامل (SSR)، يخليك PPR تدمج الاثنين في نفس استجابة HTTP. الهيكل العام للصفحة — الرأس، التذييل، التخطيط، المحتوى فوق الطي — يتخزّن كقشرة ثابتة على الـ Edge، بينما الأجزاء الديناميكية (بيانات المستخدم، الأسعار اللحظية، التوصيات المخصصة) تُبثّ من الأصل (origin) لحظة الطلب.
قبل Next.js 16 كان PPR وراء علم تجريبي اسمه experimental.ppr. مع الإصدار الثابت في أكتوبر 2025، استُبدل هذا العلم بإعداد جديد اسمه cacheComponents، وصار كل الكود ديناميكياً افتراضياً. وهذا — انتبه هنا — انعكاس كامل لسلوك Next.js 15 الذي كان ثابتاً افتراضياً. التحوّل ذهنياً صعب في البداية، بس بمجرد ما تستوعبه، الباقي سهل.
الفرق بين PPR و ISR و SSR
- SSG: صفحة كاملة مبنيّة وقت البناء. سريعة، لكنها لا تتغير.
- SSR: صفحة كاملة تُصيّر وقت الطلب. ديناميكية، لكنها تنتظر أبطأ مصدر بيانات.
- ISR: تقريباً SSG، مع إعادة توليد دوريّة بعد فترة محددة.
- PPR: قشرة ثابتة فورية + ثقوب ديناميكية تُبثّ تدريجياً ضمن استجابة واحدة. الأفضل من العالمين.
تفعيل Cache Components و PPR في next.config.ts
طيب، خلينا نبدأ بالعمل. الخطوة الأولى تفعيل المعمارية الجديدة في ملف الإعدادات. والجميل في الأمر: علم واحد فقط يفتح PPR وتوجيهات التخزين الجديدة معاً.
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true, // يفعّل PPR وتوجيهات "use cache"
};
export default nextConfig;
تشغيل cacheComponents: true يقوم بأمرين معاً: (1) يجعل جلب البيانات يحدث وقت الطلب افتراضياً، و(2) يفعّل PPR كاستراتيجية تصيير افتراضية لأي صفحة تحتوي على حدّ <Suspense>. هذا كل ما تحتاجه — لا توجد إعدادات إضافية مخفية.
النموذج الذهني: داخل Suspense ديناميكي، خارجه ثابت
هذي القاعدة احفظها حرفياً، لأنها ستخدمك في كل مرة تكتب فيها صفحة جديدة: كل شيء خارج <Suspense> يصبح جزءاً من القشرة الثابتة. كل شيء داخله يصبح ثقباً ديناميكياً. هذا التقسيم هو اللي يخبر المُصرّف (compiler) كيف يقسّم الصفحة وقت البناء.
خلّيني أوريك مثال عملي:
// app/products/[id]/page.tsx
import { Suspense } from "react";
import ProductDetails from "@/components/ProductDetails";
import LivePrice from "@/components/LivePrice";
import RecommendationsSkeleton from "@/components/RecommendationsSkeleton";
import Recommendations from "@/components/Recommendations";
export default function ProductPage({
params,
}: {
params: { id: string };
}) {
return (
<main>
{/* قشرة ثابتة: تُخزّن على Edge */}
<header>
<h1>تفاصيل المنتج</h1>
</header>
<ProductDetails id={params.id} />
{/* ثقب ديناميكي #1: السعر اللحظي */}
<Suspense fallback={<span>جارٍ تحميل السعر…</span>}>
<LivePrice id={params.id} />
</Suspense>
{/* ثقب ديناميكي #2: توصيات شخصية */}
<Suspense fallback={<RecommendationsSkeleton />}>
<Recommendations userId="auto" />
</Suspense>
</main>
);
}
توجيه "use cache" لتثبيت بيانات داخل ثقب ديناميكي
أحياناً تبغى تجلب بيانات بطيئة مرّة واحدة، ثم تشاركها بين كل المستخدمين. هنا يدخل توجيه "use cache" على الخط:
// app/lib/products.ts
import { unstable_cacheLife as cacheLife } from "next/cache";
export async function getProduct(id: string) {
"use cache";
cacheLife("hours"); // يبقى المُخزّن لساعات
const res = await fetch(`https://api.example.com/products/${id}`);
return res.json();
}
قراءة مخرجات البناء: ما معنى الرموز ◐ و ○ و ●؟
عند تشغيل next build ستلاحظ رموزاً جديدة بجوار كل مسار. فهمها مهم — لأنه الطريقة الوحيدة اللي تتأكد فيها أن صفحاتك فعلاً تستفيد من PPR وليست ديناميكية بالكامل بالغلط.
Route (app) Size First Load JS
─────────────────────────────────────────────────
◐ /products/[id] 4.2 kB 89 kB
○ /about 1.1 kB 82 kB
● /blog/[slug] 2.3 kB 84 kB
λ /api/checkout 0 B 0 B
○ Static ● SSG ◐ Partial Prerendering λ Dynamic
إذا توقعت رمز ◐ ولكن وجدت λ، فهذا يعني أن في مكان ما هناك استدعاء لـ cookies() أو headers() أو connection() خارج حدّ <Suspense>، مما أجبر الصفحة كلها على الديناميكية. صادفت هذه المشكلة شخصياً مرتين، وفي كلتا الحالتين كان السبب layout غير منتبه له.
أنماط عملية: متى تستخدم PPR ومتى تتجنبه؟
1. صفحة منتج في متجر إلكتروني
هذا هو السيناريو الذهبي بلا منازع. تفاصيل المنتج (الاسم، الصور، الوصف) ثابتة لساعات، بينما السعر والمخزون والتوصيات الشخصية ديناميكية. PPR يمنحك الأفضل من العالمين هنا.
2. لوحة تحكم المستخدم
الإطار العام (الشريط الجانبي، شعار الموقع، روابط التنقل) ثابت تماماً. الإحصائيات والإشعارات تُلَف داخل <Suspense>. النتيجة؟ المستخدم يرى الواجهة فوراً ويبدأ التنقل قبل اكتمال البيانات. تجربة مستخدم أفضل بمراحل.
3. متى لا تستخدم PPR؟
- صفحات مكوّنة بالكامل من محتوى شخصي (مثلاً
/account/billing): لا فائدة من قشرة ثابتة لأن كل شيء يعتمد على الكوكيز. - صفحات API: لا تصيّر HTML أصلاً.
- مسارات تستخدم
headers()في الـ layout الجذر — ستتعطل PPR لكل المسارات الفرعية. وهذه نقطة كثير من المطورين يقعون فيها.
الترحيل من Next.js 15 إلى Next.js 16: ماذا يتغيّر؟
- احذف
experimental.pprمنnext.config.tsواستبدلها بـcacheComponents: true. - أعد تسمية
middleware.tsإلىproxy.ts— تغيير كاسر آخر في Next.js 16، ولا، لا يوجد توافق رجعي. - راجع أي
export const dynamic = "force-static"— لم يعد ضرورياً مع المعمارية الجديدة. - اختبر الـ build وراقب الرموز ◐ مقابل λ بعين الصقر.
- لفّ أي استدعاء لـ
cookies()/headers()داخل مكوّن ضمن<Suspense>.
قياس الأداء الفعلي: أرقام من بيئة الإنتاج
طبعاً، الكلام عن الأداء بدون أرقام مجرد ادعاءات. وفقاً لتقارير المطورين الذين رحّلوا تطبيقات تجارية إلى Next.js 16 في الربع الأول من 2026:
- TTFB (Time To First Byte): انخفاض بين 60% و 85% لأن القشرة تأتي من Edge cache مباشرة.
- LCP (Largest Contentful Paint): تحسّن متوسط بنسبة 35% للصفحات التي كانت تستخدم SSR كاملاً.
- تكاليف الـ Compute: انخفاض بحدود 40% على Vercel، لأن أجزاء كبيرة من الصفحة لم تعد تُصيّر في كل طلب. وهذا الرقم يهم المحاسب أكثر مما يهم المبرمج.
تشخيص المشكلات الشائعة
الخطأ: "Route had a fallback shell but is being treated as dynamic"
السبب: استدعاء API ديناميكي في الـ layout. الحل: انقل المنطق إلى مكوّن داخلي ولفّه بـ <Suspense>. بهذه البساطة.
الـ Fallback يظل ظاهراً للأبد
تحقق من أن الـ component غير المتزامن يُكمل فعلاً. أضف console.time/console.timeEnd داخله للتأكد. مشكلة شائعة جداً: fetch بدون مهلة زمنية (timeout) — وقعت فيها بنفسي أكثر من مرة.
الكاش لا يتم إبطاله بعد revalidateTag
تأكد أن الدالة المُخزّنة تستخدم cacheTag("اسم-الوسم") داخلها، وأنك تنادي revalidateTag من Server Action وليس من Client Component (هذا الفرق يخطئ فيه الكثيرون).
أسئلة شائعة (FAQ)
هل PPR متاح بدون Vercel؟
نعم، طبعاً. يعمل PPR على أي محوّل (adapter) يدعم البثّ المتدفق (streaming responses)، بما في ذلك Node.js standalone و Cloudflare Workers مع المحوّل الرسمي. الفرق على Vercel أنه يحصل تلقائياً على Edge cache للقشرة الثابتة، وهذا توفير مجاني للأداء.
ما الفرق بين "use cache" و fetch({ cache: "force-cache" })؟
التوجيه "use cache" أوسع بكثير: يطبّق على أي دالة (ليس فقط fetch)، ويدعم cacheLife و cacheTag بطريقة موحّدة وأنيقة. أما fetch القديم فمحدود بطلبات الشبكة فقط. في Next.js 16، يُنصح بـ "use cache" كاستراتيجية افتراضية.
هل يمكنني استخدام PPR مع React Server Components فقط؟
PPR لا يهتم بنوع المكوّن (Server أو Client). اللي يهمه فعلاً هو حدود <Suspense> وما إذا كانت البيانات داخلها ديناميكية. يمكن أن تحتوي القشرة الثابتة على Client Components تفاعلية بشرط ألا تعتمد على بيانات وقت الطلب.
هل PPR يحلّ محل ISR؟
لا، بل يكمّله. ISR لا يزال مفيداً لمدوّنات أو صفحات تسويق حيث كل المحتوى ثابت لكنه يحتاج إلى إعادة توليد دورية. PPR للحالات التي تجمع ثابتاً وديناميكياً في نفس الصفحة. الأدوات مختلفة لمشكلات مختلفة.
كيف أعرف أن صفحتي فعلاً تستخدم PPR في الإنتاج؟
اطلب الصفحة وراقب رؤوس الاستجابة. ابحث عن x-nextjs-cache: HIT على القشرة، و x-vercel-stale-while-revalidate أو رأس Transfer-Encoding: chunked اللي يدلّ على بثّ تدريجي. أدوات المطوّر في المتصفح ستظهر الـ HTML يصل على دفعات — منظر جميل بصراحة عندما تشوفه أول مرة.
الخلاصة
Partial Prerendering في Next.js 16 ليس مجرد تحسين أداء — إنه نموذج تفكير جديد بالكامل. توقّف عن السؤال "هل صفحتي ثابتة أم ديناميكية؟" وابدأ بالسؤال "أين الحدود بين ما يمكن تخزينه وما يجب تصييره لحظياً؟". <Suspense> هو أداتك لرسم هذه الحدود، و cacheComponents هو المفتاح اللي يفعّل كل شيء.
إذا كنت تبدأ مشروعاً جديداً في 2026، فعّل PPR من اليوم الأول. لا تفكر مرتين. أما إذا كنت تنقل مشروعاً قائماً، ابدأ بصفحة واحدة عالية الزيارات (صفحة منتج، صفحة مقال، أيّ شيء يتم طلبه آلاف المرات يومياً) وقس الفرق. الأرقام عادةً تتحدث عن نفسها — وفي تجربتي، صدمت الفريق كله.