"بطل تفكير زيادة في استراتيجيات العرض. هنا اللي فعلاً مهم لما تبني تطبيقات حقيقية مع Next.js 16، وليه اختيارك مهم أكتر ما تتخيل."
المشكلة اللي حد ما يتكلم عنها
عندك تطبيق Next.js. محتاج تعرض حاجة. يبدو بسيط، صح؟ لكن مش بسيط خالص. عندك ثلاث طرق رئيسية—Static Site Generation و Incremental Static Regeneration و Server-Side Rendering—واختيار الطريقة الغلط هيقعد يزعجك بعدين.
الحقيقة: معظم الدروس بتتعامل مع دي الحاجات زي مفاهيم أكاديمية. لكن هي مش كدا. هي قرارات عملية بتأثر على تكاليف البنية التحتية بتاعتك، على تجربة المستخدم، وعلى كام ساعة نوم هتخسرها وأنت بتحاول تحل مشاكل الـ cache الساعة اتنين الفجر.
Static Site Generation: الطريق السريع
SSG هو الوحش السريع. بتبني صفحاتك مرة واحدة، في وقت البناء، وبعدين بتخدم نفس الـ HTML لكل الناس. أقرب حاجة لـ "أداء مجاني" هتلاقيها.
متى ده فعلاً بيشتغل؟
مقالات المدونة. التوثيق. صفحات التسويق. أي حاجة ما بتتغير كل خمس دقائق. لو المحتوى بتاعك مستقر نسبياً، SSG هو صديقك.
// app/blog/page.tsx
export const dynamic = 'force-static'
export const revalidate = false
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
cache: 'force-cache'
})
return res.json()
}
export default async function BlogPage() {
const posts = await getPosts()
return (
<div>
<h1>مقالات المدونة</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
)
}
الجمال هنا؟ السيرفر بتاعك ما بيتعب خالص. الـ CDNs بتحب الحاجات دي. الفاتورة بتاعتك تقعد معقولة. بس في مشكلة—لو حدثت مقالة، حد ما بيشوفها إلا لما تعيد البناء. ده هو الثمن.
المميزات الحقيقية:
- سرعة جنونية (نتكلم عن ميلي ثانية)
- السيرفر بتاعك بيتحمل الضغط بسهولة
- الـ SEO بيحبك
- التكاليف تقعد منخفضة
العيوب الحقيقية:
- تحديثات المحتوى تحتاج rebuild
- مش كويس للبيانات اللي بتتغير كتير
- أوقات البناء ممكن تصير مجنونة لما يكون عندك محتوى كتير
Incremental Static Regeneration: الطريق الوسط
ISR هو حيث الأشياء بتصير مثيرة. بتاخد سرعة SSG، بس مع المرونة إنك تحدث المحتوى بدون ما تعيد بناء كل حاجة. زي ما تاكل الكيك وتحتفظ بيه في نفس الوقت—تقريباً.
الفكرة: بتبني الصفحات في وقت البناء، بس بتعيد بناؤها بشكل دوري أو عند الطلب لما يتغير المحتوى.
// app/products/page.tsx
export const dynamic = 'auto'
export const revalidate = 3600 // إعادة التحقق كل ساعة
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 }
})
return res.json()
}
export default async function ProductsPage() {
const products = await getProducts()
return (
<div>
<h1>المنتجات</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
)
}
لما تتعامل مع استعلامات قاعدة البيانات، استخدم unstable_cache مع الوسوم عشان تقدر تلغي الـ cache عند الطلب:
// app/products/page.tsx
import { unstable_cache } from 'next/cache'
import { db } from '@/lib/db'
const getCachedProducts = unstable_cache(
async () => {
return await db.select().from('products').limit(10)
},
['products'],
{ revalidate: 3600, tags: ['products'] }
)
export default async function ProductsPage() {
const products = await getCachedProducts()
return (
<div>
<h1>المنتجات</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
)
}
هنا بتبدأ تفكر في cache invalidation. وآه، Phil Karlton قال "في حاجتين بس صعبة في علم الحاسوب: cache invalidation وتسمية الحاجات". ما كان غلط.
ليه ISR بيشتغل:
- تحميل أولي سريع (معروض مسبقاً)
- تحديثات المحتوى بدون rebuild كامل
- استراتيجيات إعادة تحقق مرنة
- يتسع بشكل معقول
حيث الأشياء بتصير معقدة:
- محتوى قديم شوية بين إعادات التحقق
- إعداد أكتر تعقيداً من SSG البحت
- محتاج تفكر في cache tags والـ invalidation
Server-Side Rendering: خيار البيانات الفورية
SSR هو نهج "دائماً محدث". كل طلب بيضرب السيرفر، بيبني الصفحة، وبيبعتها. مرن، بس فيه ثمن.
استخدم ده لما تحتاج فعلاً بيانات فورية. لوحات تحكم المستخدم. محتوى شخصي. أي حاجة بتتغير مع كل طلب.
// app/dashboard/page.tsx
export const dynamic = 'force-dynamic'
export const revalidate = 0
async function getUserData(userId: string) {
const res = await fetch(`https://api.example.com/users/${userId}`, {
cache: 'no-store'
})
return res.json()
}
export default async function DashboardPage() {
const user = await getUserData('current-user')
return (
<div>
<h1>مرحبًا، {user.name}</h1>
<p>آخر تحديث: {new Date().toLocaleString('ar-SA')}</p>
</div>
)
}
استعلامات قاعدة البيانات المباشرة بتشتغل كمان:
// app/orders/page.tsx
import { db } from '@/lib/db'
async function getUserOrders(userId: string) {
return await db
.select()
.from('orders')
.where('userId', '=', userId)
}
export default async function OrdersPage() {
const orders = await getUserOrders('current-user')
return (
<div>
<h1>طلباتك</h1>
<ul>
{orders.map((order) => (
<li key={order.id}>
الطلب #{order.id} - {order.total} ريال
</li>
))}
</ul>
</div>
)
}
الجانب الإيجابي:
- بيانات محدثة دائماً
- مثالي للتجارب الشخصية
- ما في مشاكل محتوى قديم
الجانب السلبي:
- السيرفر بتاعك بيتعرض للضغط
- أوقات الاستجابة أبطأ
- ما بيتسع بشكل كويس
- تكاليف البنية التحتية بتطلع
تكوين مستوى الصفحة: حيث السحر بيحصل
Next.js 16 بيخليك تكون سلوك العرض مباشرة على مستوى الصفحة. هنا بتاخد القرارات الفعلية.
لصفحات SSG:
// app/blog/page.tsx
export const dynamic = "force-static";
export const revalidate = false;
لصفحات ISR:
// app/products/page.tsx
export const dynamic = "auto";
export const revalidate = 3600;
لصفحات SSR:
// app/dashboard/page.tsx
export const dynamic = "force-dynamic";
export const revalidate = 0;
التصديرات دي بتقول لـ Next.js بالظبط إزاي تتعامل مع كل صفحة. بدون تخمين. بدون سحر.
المسارات الديناميكية: حيث الأشياء بتصير حقيقية
المسارات الديناميكية هي حيث معظم الناس بتتلخبط. عندك مدونة فيها آلاف المقالات. ما تقدر تعرض كل واحدة مسبقاً في وقت البناء—ده هياخد أبد الدهر.
هنا بيجي generateStaticParams. بيخليك تحدد أي قطاعات ديناميكية تعرضها مسبقاً.
SSG مع المسارات الديناميكية
// app/blog/[slug]/page.tsx
export const dynamic = 'force-static'
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
cache: 'force-cache'
})
return res.json()
}
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(
(res) => res.json()
)
return posts.map((post) => ({
slug: post.slug,
}))
}
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await getPost(slug)
return {
title: post.title,
description: post.excerpt,
}
}
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await getPost(slug)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
ISR مع المسارات الديناميكية
عرض مسبق للحاجات الشهيرة، وبعدين اعرض الباقي عند الطلب:
// app/products/[id]/page.tsx
export const dynamic = 'auto'
export const dynamicParams = true
export const revalidate = 3600
async function getProduct(id: string) {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { revalidate: 3600, tags: ['product'] }
})
return res.json()
}
export async function generateStaticParams() {
// عرض مسبق للمنتجات الشهيرة بس
const products = await fetch('https://api.example.com/products?popular=true').then(
(res) => res.json()
)
return products.map((product) => ({
id: product.id.toString(),
}))
}
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const product = await getProduct(id)
return (
<div>
<h1>{product.name}</h1>
<p>{product.price} ريال</p>
<p>{product.description}</p>
</div>
)
}
SSR مع المسارات الديناميكية
لما تحتاج بيانات فورية لكل طلب:
// app/orders/[orderId]/page.tsx
export const dynamic = 'force-dynamic'
export const dynamicParams = true
async function getOrder(orderId: string) {
const res = await fetch(`https://api.example.com/orders/${orderId}`, {
cache: 'no-store'
})
return res.json()
}
export default async function OrderPage({ params }: { params: Promise<{ orderId: string }> }) {
const { orderId } = await params
const order = await getOrder(orderId)
return (
<div>
<h1>الطلب #{order.id}</h1>
<p>الحالة: {order.status}</p>
<p>الإجمالي: {order.total} ريال</p>
<p>آخر تحديث: {new Date().toLocaleString('ar-SA')}</p>
</div>
)
}
التحكم في سلوك الاحتياطي
إيه اللي بيحصل لما حد يطلب قطاع ديناميكي ما تم عرضه مسبقاً؟ أنت اللي تقرر:
// app/blog/[slug]/page.tsx
export const dynamic = "auto";
export const dynamicParams = false; // إرجع 404 للمقالات غير المعروفة
export async function generateStaticParams() {
const posts = await fetch("https://api.example.com/posts").then((res) =>
res.json(),
);
return posts.map((post) => ({
slug: post.slug,
}));
}
حط dynamicParams = false والمسارات غير المعروفة بتاخد 404. حطها true وبتتعرض عند الطلب. قرارك.
ورقة الغش للتكوين
| التصدير | SSG | ISR | SSR |
|---|---|---|---|
dynamic | 'force-static' | 'auto' | 'force-dynamic' |
revalidate | false | 3600 (أو رقم) | 0 |
dynamicParams | false | true | true |
مصفوفة المقايضات
| الميزة | SSG | ISR | SSR |
|---|---|---|---|
| السرعة | الأسرع | سريع | أبطأ |
| حداثة البيانات | قديمة | قابلة للتكوين | محدثة دائماً |
| حمل السيرفر | أدنى | منخفض | عالي |
| القابلية للتوسع | ممتازة |
الواقع الهجين
التطبيقات الحقيقية ما بتختار واحد. بتستخدم الثلاثة.
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html>
<body>
{/* رأس ثابت - SSG */}
<Header />
{/* محتوى ديناميكي - SSR */}
{children}
{/* شريط جانبي مخزن مؤقتاً - ISR */}
<Sidebar />
</body>
</html>
)
}
الرأس بتاعك؟ ثابت. المحتوى الرئيسي؟ يعتمد على الصفحة. الشريط الجانبي؟ مخزن مؤقتاً وبيتحدث كل ساعة. ده إزاي بتبني تطبيقات بأداء عالي.
الخلاصة العملية
ابدأ بـ SSG. هو الخيار الأسرع والأرخص. لما تحتاج حداثة، أضف ISR. بس اطلب SSR لما تحتاج فعلاً بيانات فورية مع كل طلب.
ما تفكر زيادة. اختار أبسط استراتيجية تحل المشكلة بتاعتك. نسختك المستقبلية من نفسك هتشكرك.
المصادر:
ترميز سعيد! — Ahmed Fahmy