"Next.js 16 غيّر اللعبة مع Cache Components. هنا ليه ده مش بس استراتيجية عرض أخرى—ده نهج مختلف تماماً لبناء تطبيقات سريعة."
الطريقة القديمة مقابل الطريقة الجديدة
لسنين، كنا بنختار بين SSG و ISR و SSR. وقت البناء مقابل وقت الطلب. ثابت مقابل ديناميكي. كان فيه تفكير ثنائي.
بعدين Next.js 16 قدم Cache Components مع Partial Pre-Rendering (PPR). وفجأة، الاختيار الثنائي ده ما عاد مهم.
الحقيقة: Cache Components مش بس استراتيجية عرض أخرى. ده نموذج مختلف تماماً. بدل ما تختار طريقة واحدة لكل صفحة، بتقدر تخلط محتوى ثابت ومخزن مؤقتاً وديناميكي في نفس المسار. فوراً.
إيه اللي بيخليها مختلفة
مع SSG و ISR و SSR، بتاخد قرار على مستوى الصفحة. الصفحة كلها ثابتة، أو بتتجدد بشكل دوري، أو بتتعرض مع كل طلب.
Cache Components بتقلب الحاجة. بتاخد قرارات على مستوى المكون.
// app/dashboard/page.tsx
export default function DashboardPage() {
return (
<>
{/* Static shell - فوري من الـ CDN */}
<header><h1>لوحة التحكم</h1></header>
<nav>...</nav>
{/* Cached - سريع، بيتحدث كل ساعة */}
<Stats />
{/* Dynamic - بيتدفق مع بيانات محدثة */}
<Suspense fallback={<NotificationsSkeleton />}>
<Notifications />
</Suspense>
</>
)
}
شفت إيه اللي حصل؟ الرأس ثابت. الإحصائيات مخزنة مؤقتاً. الإشعارات ديناميكية. كل دي في صفحة واحدة. كل دي بتشتغل مع بعضها.
ده هو قوة Cache Components.
التوجيه use cache: صديقك الجديد الأفضل
بدل ما تفكر في وقت البناء مقابل وقت الطلب، بتفكر في إيه اللي محتاج يتخزن مؤقتاً.
async function Stats() {
'use cache'
cacheLife('hours')
cacheTag('dashboard-stats')
const stats = await db.stats.aggregate()
return <StatsDisplay stats={stats} />
}
خلاص كده. توجيه واحد. المكون مخزن مؤقتاً لمدة ساعة. لما تنتهي الساعة، بيتحدث في الخلفية. المستخدمين بيشوفوا بيانات محدثة بدون ما ينتظروا.
التنقل الفورى: الفوز الحقيقي
هنا حيث Cache Components بتلمع: التنقل الفورى.
مع SSG/ISR/SSR، بتنتظر السيرفر يعرض. حتى مع ISR، في لحظة الصفحة قديمة. مع SSR، بتنتظر دائماً.
Cache Components بتعرض الـ static shell في وقت البناء. الرأس، الـ layout، شاشات الـ skeleton—كل دي فوري. بعدين الأجزاء المخزنة مؤقتاً والديناميكية بتتدفق.
export default function Page() {
return (
<>
{/* ده بيعرض فوراً من الـ static shell */}
<Header />
<Navigation />
{/* ده بيتدفق بعدين */}
<Suspense fallback={<Skeleton />}>
<DynamicContent />
</Suspense>
</>
)
}
المستخدمين بيشوفوا حاجة فوراً. ما في صفحة بيضاء. ما في spinner تحميل. بس محتوى فوري، والأجزاء الديناميكية بتملأ الفراغات وهي بتوصل.
ده تجربة مستخدم أفضل بشكل أساسي.
إلغاء التخزين المؤقت: أخيراً، بسيط
تذكر لما قلت إن cache invalidation هي واحدة من أصعب مفهومين في علم الحاسوب؟ Cache Components بتخليها أقل ألماً.
عندك أداتين: updateTag لإلغاء فوري، و revalidateTag لإعادة تحقق في الخلفية.
إلغاء فوري مع updateTag
استخدم ده لما تحتاج المستخدم يشوف التغييرات بتاعه فوراً:
"use server";
import { updateTag } from "next/cache";
export async function updateProduct(id: string, data: FormData) {
await db.products.update({ where: { id }, data });
updateTag(`product-${id}`); // فوري - المستخدم بيشوف بيانات محدثة
}
المستخدم بيرسل فورم، البيانات بتتحدث، والـ cache بيتلغى فوراً. بيشوفوا التغييرات بتاعهم فوراً.
إعادة تحقق في الخلفية مع revalidateTag
استخدم ده لما تكون eventual consistency كويسة:
"use server";
import { revalidateTag } from "next/cache";
export async function createPost(data: FormData) {
await db.posts.create({ data });
revalidateTag("posts", "max"); // خلفية - الطلب الجاي بيشوف بيانات محدثة
}
الـ post بيتنشئ، الـ cache بيتعلم للتحديث، والطلب الجاي بيشوف بيانات محدثة. بدون انتظار. بدون blocking.
إزاي بتقارن مع SSG و ISR و SSR
الميزة | SSG | ISR | SSR | Cache Components |
|---|---|---|---|---|
وقت البناء | بطيء | متوسط | سريع | متوسط |
التحميل الأولي | الأسرع | سريع | أبطأ | الأسرع (static shell) |
حداثة البيانات | قديمة | قابلة للتكوين | محدثة دائماً | مختلطة (لكل مكون) |
حمل السيرفر | أدنى | منخفض | عالي | منخفض |
القابلية للتوسع | ممتازة | جيدة | محدودة | ممتازة |
التعقيد | منخفض | متوسط | منخفض | متوسط |
تجربة المستخدم | جيدة | جيدة | الأفضل | الأفضل |
التكلفة | منخفضة | منخفضة-متوسطة | عالية | منخفضة |
الفرق الرئيسي؟ Cache Components بتديك الأفضل من كل الحاجات. السرعة الفورية لـ SSG، حداثة SSR، وكفاءة ISR—كل دي في صفحة واحدة.
مثال من الحياة الحقيقية: صفحة منتج
قول إنك بتبني موقع تجارة إلكترونية. محتاج:
رأس منتج ثابت (ما بيتغير كتير)
تفاصيل منتج مخزنة مؤقتاً (بتتغير أحياناً)
تقييمات مستخدم ديناميكية (بتتغير كتير)
مخزون فوري (بيتغير باستمرار)
مع SSG/ISR/SSR، كان لازم تختار استراتيجية واحدة للصفحة كلها. غالباً SSR، يعني كل طلب بيضرب قاعدة البيانات.
مع Cache Components:
export default function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
return (
<>
{/* ثابت - فوري */}
<ProductHeader productId={id} />
{/* Cached - بيتحدث كل ساعة */}
<ProductDetails productId={id} />
{/* Dynamic - محدث دائماً */}
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews productId={id} />
</Suspense>
{/* Real-time - بيتدفق فوراً */}
<Suspense fallback={<InventorySkeleton />}>
<InventoryStatus productId={id} />
</Suspense>
</>
)
}
async function ProductHeader({ productId }: { productId: string }) {
'use cache'
cacheLife('max') // ما بيتغير أبداً
return <header>المنتج #{productId}</header>
}
async function ProductDetails({ productId }: { productId: string }) {
'use cache'
cacheLife('hours')
cacheTag(`product-${productId}`)
const product = await db.products.findUnique({ where: { id: productId } })
return <div>{product.description}</div>
}
async function ProductReviews({ productId }: { productId: string }) {
const reviews = await db.reviews.findMany({ where: { productId } })
return <div>{reviews.map(r => <Review key={r.id} review={r} />)}</div>
}
async function InventoryStatus({ productId }: { productId: string }) {
const inventory = await db.inventory.findUnique({ where: { productId } })
return <div>متوفر: {inventory.count}</div>
}
الصفحة بتحمل فوراً مع الـ static shell. تفاصيل المنتج بتظهر بسرعة من الـ cache. التقييمات والمخزون بيتدفقوا مع بيانات محدثة.
ده مش ممكن مع استراتيجيات العرض التقليدية. ده Cache Components.
المشكلة: Runtime APIs
في مشكلة واحدة. جوا use cache، ما تقدر تستخدم runtime APIs زي cookies() أو headers() أو searchParams.
// ده ما بيشتغل
async function CachedProfile() {
'use cache'
const session = (await cookies()).get('session')?.value // خطأ!
return <div>{session}</div>
}
بس في حل. مرر البيانات كـ props:
// ده بيشتغل
async function ProfilePage() {
const session = (await cookies()).get('session')?.value
return <CachedProfile sessionId={session} />
}
async function CachedProfile({ sessionId }: { sessionId: string }) {
'use cache'
const data = await fetchUserData(sessionId)
return <div>{data.name}</div>
}
أو استخدم use cache: private لو محتاج runtime APIs:
async function getData() {
"use cache: private";
const session = (await cookies()).get("session")?.value; // مسموح
return fetchData(session);
}
متى تستخدم Cache Components
Cache Components بتلمع لما يكون عندك محتوى مختلط. أجزاء ما بتتغير، أجزاء بتتغير أحياناً، أجزاء محتاجة تكون محدثة.
لو الصفحة كلها ثابتة؟ SSG أبسط.
لو الصفحة كلها ديناميكية؟ SSR كويس.
بس لو عندك خليط؟ Cache Components هي الإجابة.
طريق الهجرة
لو أنت على Next.js 16، تفعيل Cache Components بسيط:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;
بعدين ابدأ تستخدم use cache في المكونات بتاعتك. ما تحتاج تهاجر كل حاجة في مرة واحدة. بتقدر تتبناها بشكل تدريجي.
الخلاصة
Cache Components ما بتستبدل SSG أو ISR أو SSR. ده أداة جديدة في صندوق الأدوات بتاعتك. أداة بتخليك تبني صفحات أسرع وأكتر كفاءة بدون تعقيد الاختيار بين استراتيجيات عرض متعددة.
ده مستقبل عرض Next.js. وبصراحة؟ ده جميل جداً.
المصادر:
ترميز سعيد! — Ahmed Fahmy