"بطل تفكير زيادة في استراتيجيات العرض. هنا اللي فعلاً مهم لما تبني تطبيقات حقيقية مع Next.js 16، وليه اختيارك مهم أكتر ما تتخيل."
ترميز سعيد! — Ahmed Fahmy
عندك تطبيق Next.js. محتاج تعرض حاجة. يبدو بسيط، صح؟ لكن مش بسيط خالص. عندك ثلاث طرق رئيسية—Static Site Generation و Incremental Static Regeneration و Server-Side Rendering—واختيار الطريقة الغلط هيقعد يزعجك بعدين.
الحقيقة: معظم الدروس بتتعامل مع دي الحاجات زي مفاهيم أكاديمية. لكن هي مش كدا. هي قرارات عملية بتأثر على تكاليف البنية التحتية بتاعتك، على تجربة المستخدم، وعلى كام ساعة نوم هتخسرها وأنت بتحاول تحل مشاكل الـ cache الساعة اتنين الفجر.
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 بتحب الحاجات دي. الفاتورة بتاعتك تقعد معقولة. بس في مشكلة—لو حدثت مقالة، حد ما بيشوفها إلا لما تعيد البناء. ده هو الثمن.
المميزات الحقيقية:
العيوب الحقيقية:
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 بيشتغل:
حيث الأشياء بتصير معقدة:
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. بيخليك تحدد أي قطاعات ديناميكية تعرضها مسبقاً.
// 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>
)
}
عرض مسبق للحاجات الشهيرة، وبعدين اعرض الباقي عند الطلب:
// 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>
)
}
لما تحتاج بيانات فورية لكل طلب:
// 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 |
fetchCache | 'force-cache' | 'auto' | 'force-no-store' |
| الميزة | 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 لما تحتاج فعلاً بيانات فورية مع كل طلب.
ما تفكر زيادة. اختار أبسط استراتيجية تحل المشكلة بتاعتك. نسختك المستقبلية من نفسك هتشكرك.
المصادر: