Next.js
قراءة في 7 دقائقاختيار استراتيجية العرض المناسبة في Next.js
إطار عملي لاختيار العرض الثابت والديناميكي على الخادم وإعادة التحقق والتفاعلات على العميل في App Router.

العرض قرار يُتخذ لكل مسار، وأحيانًا لكل مصدر بيانات. يناسب الناتج الثابت المحتوى العام، ويحتاج الرد المخصص إلى عرض وقت الطلب، ويوازن ISR بين الحداثة وإعادة الاستخدام، بينما يناسب الجلب على العميل التفاعلات التي يديرها المتصفح. الأمثلة أنماط قابلة للتكييف وليست إعدادات للنسخ الأعمى؛ تحقق من إصدار Next.js المستخدم.
١. التوليد الثابت (SSG)
استخدم التوليد الثابت للصفحات العامة التي يمكن تجهيزها قبل الطلب، مثل صفحة التعريف والتوثيق والملف الشخصي والمقالات المنشورة. يقلل العمل على الخادم لكل طلب ويمكن تقديمه من CDN. غالبًا ما تُعرض صفحات App Router التي لا تستخدم واجهات وقت الطلب بشكل ثابت افتراضيًا؛ وforce-static خيار صريح وليس مفتاح أداء سحريًا.
// Public content that changes only when you publish a new build
import { posts } from "@/blog/data/posts";
export const dynamic = "force-static";
export default function BlogPage() {
return <BlogList posts={posts} />;
}٢. إعادة التوليد الثابت التدريجي (ISR)
يفيد ISR عندما تتغير البيانات العامة دون الحاجة إلى إنشاء استجابة جديدة من المصدر لكل زائر، مثل قوائم المنتجات والتفاصيل العامة والأخبار. تعبر revalidate = 300 عن نافذة حداثة خمس دقائق وفق دلالات الإصدار ومنصة النشر. قد يكون الإبطال حسب الوسم أو المسار أدق عند النشر من انتظار انتهاء المدة.
// Reuse the generated response and refresh it at most every 5 minutes
export const revalidate = 300;
export default async function ProductsPage() {
const products = await getPublicProducts();
return <ProductGrid products={products} />;
}٣. العرض الديناميكي على الخادم (SSR)
استخدم العرض وقت الطلب عندما تعتمد النتيجة على الجلسة أو ملفات الارتباط أو الترويسات. يقرأ المثال الجلسة ثم يجلب حساب المستخدم. لا تخزن HTML أو بيانات شخصية في ذاكرة مشتركة. كما أن فرض العرض الديناميكي لا يغني عن التحقق من الصلاحيات داخل كل عملية بيانات حساسة.
import { cookies } from "next/headers";
export const dynamic = "force-dynamic";
export default async function AccountPage() {
const cookieStore = await cookies();
const session = await readSession(cookieStore);
if (!session) redirect("/login");
const account = await getAccount(session.userId);
return <AccountDashboard account={account} />;
}٤. العرض والجلب على العميل (CSR)
يفيد الجلب على العميل للبيانات المتغيرة أو المرتبطة بتفاعل بعد تحميل الصفحة، مثل سعر مباشر أو مرشح يتحكم فيه المستخدم. اجعل حدود العميل صغيرة، وأضف حالات التحميل والخطأ، وألغِ الطلبات القديمة ولا تكشف أسرار الخادم. يمكن لمكتبة جلب البيانات إضافة التخزين وإزالة التكرار وإعادة المحاولة والتحقق عند التركيز بشكل أفضل.
"use client";
import { useEffect, useState } from "react";
type Quote = { symbol: string; price: number };
export function LiveStock({ symbol }: { symbol: string }) {
const [quote, setQuote] = useState<Quote | null>(null);
const [error, setError] = useState(false);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/quotes/${symbol}`, { signal: controller.signal })
.then((r) => { if (!r.ok) throw new Error(); return r.json(); })
.then(setQuote)
.catch((e) => { if (e.name !== "AbortError") setError(true); });
return () => controller.abort();
}, [symbol]);
if (error) return <p role="alert">Quote unavailable.</p>;
if (!quote) return <p aria-live="polite">Loading quote…</p>;
return <p>{quote.symbol}: {quote.price}</p>;
}٥. العرض المسبق للمسارات الديناميكية
تسمح generateStaticParams بتجهيز المسارات المعروفة أثناء البناء. تعني dynamicParams = false أن المسارات غير المعروفة تعيد 404 بدل إنشائها عند الطلب. لا تستخدم ذلك لمجموعة غير محدودة أو كثيرة التغير دون مراعاة وقت البناء وإعادة التحقق.
import { posts } from "@/blog/data/posts";
export function generateStaticParams() {
return posts.map((post) => ({ slug: post.slug }));
}
export const dynamicParams = false; // 404 for slugs not generated above٦. إعادة التحقق بعد تعديل البيانات
عند نشر منتج قد يبقي الانتظار الزمني الناتج القديم ظاهرًا. بعد نجاح الكتابة في قاعدة البيانات، أبطل الوسم أو المسار المناسب. يجب أن يطابق الوسم المستخدم في جلب البيانات، وتحقق من توقيع API وفق إصدار Next.js لأن واجهات التخزين تغيرت بين الإصدارات.
"use server";
import { revalidatePath, revalidateTag } from "next/cache";
export async function publishProduct(id: string) {
await updateProductInDatabase(id);
revalidateTag("products");
revalidatePath("/products");
}٧. اختر وفق نوع الصفحة
- الملف الشخصي والتوثيق والمقالة المنشورة ← SSG، مع إعادة التحقق عند التحديث.
- كتالوج عام يتغير دوريًا ← ISR أو إبطال حسب الوسم أو المسار.
- الحساب والفوترة وبيانات الإدارة ← عرض وقت الطلب مع تفويض على الخادم؛ لا تخزن المخرجات الخاصة بشكل مشترك.
- المرشحات والرسوم المتغيرة ← Client Component صغير مع مكتبة استعلامات غالبًا.
٨. تحقق في بناء الإنتاج
شغّل next build وافحص ملخص المسارات بدل افتراض أن الصفحة ثابتة لأن كودها بسيط. اختبر ببيانات واقعية ومصادقة وخدمات بطيئة وبيئة منشورة. العرض موازنة بين الحداثة والكمون وتكلفة الخادم وأمان التخزين وتجربة المستخدم، وليس سباقًا لجعل كل المسارات ثابتة.