getStaticPaths

عند تصدير دالة تُسمى getStaticPaths من صفحة تستخدم المسارات الديناميكية (Dynamic Routes)، فإن Next.js سيقوم بتحضير جميع المسارات المحددة بواسطة getStaticPaths بشكل ثابت مسبقًا.

import type {
  InferGetStaticPropsType,
  GetStaticProps,
  GetStaticPaths,
} from 'next'

type Repo = {
  name: string
  stargazers_count: number
}

export const getStaticPaths = (async () => {
  return {
    paths: [
      {
        params: {
          name: 'next.js',
        },
      }, // انظر قسم "paths" أدناه
    ],
    fallback: true, // false أو "blocking"
  }
}) satisfies GetStaticPaths

export const getStaticProps = (async (context) => {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const repo = await res.json()
  return { props: { repo } }
}) satisfies GetStaticProps<{
  repo: Repo
}>

export default function Page({
  repo,
}: InferGetStaticPropsType<typeof getStaticProps>) {
  return repo.stargazers_count
}
export async function getStaticPaths() {
  return {
    paths: [
      {
        params: {
          name: 'next.js',
        },
      }, // انظر قسم "paths" أدناه
    ],
    fallback: true, // false أو "blocking"
  }
}

export async function getStaticProps() {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const repo = await res.json()
  return { props: { repo } }
}

export default function Page({ repo }) {
  return repo.stargazers_count
}

قيم إرجاع getStaticPaths

يجب أن تُرجع الدالة getStaticPaths كائنًا يحتوي على الخصائص المطلوبة التالية:

paths

يحدد مفتاح paths المسارات التي سيتم تحضيرها مسبقًا. على سبيل المثال، لنفترض أن لديك صفحة تستخدم المسارات الديناميكية (Dynamic Routes) باسم pages/posts/[id].js. إذا قمت بتصدير getStaticPaths من هذه الصفحة وأعدت ما يلي لـ paths:

return {
  paths: [
    { params: { id: '1' }},
    {
      params: { id: '2' },
      // مع تكوين i18n، يمكن أيضًا إرجاع اللغة للمسار
      locale: "en",
    },
  ],
  fallback: ...
}

عندها، سيقوم Next.js بإنشاء /posts/1 و /posts/2 بشكل ثابت أثناء next build باستخدام مكون الصفحة في pages/posts/[id].js.

يجب أن تتطابق قيمة كل كائن params مع المعلمات المستخدمة في اسم الصفحة:

  • إذا كان اسم الصفحة هو pages/posts/[postId]/[commentId]، فيجب أن يحتوي params على postId و commentId.
  • إذا كان اسم الصفحة يستخدم مسارات التقاط الكل (catch-all routes) مثل pages/[...slug]، فيجب أن يحتوي params على slug (وهو مصفوفة). إذا كانت هذه المصفوفة ['hello', 'world']، فسوف يقوم Next.js بإنشاء الصفحة بشكل ثابت على /hello/world.
  • إذا كانت الصفحة تستخدم مسار التقاط اختياري (optional catch-all route)، استخدم null أو [] أو undefined أو false لعرض المسار الجذري. على سبيل المثال، إذا قمت بتوفير slug: false لـ pages/[[...slug]]، فسوف يقوم Next.js بإنشاء الصفحة / بشكل ثابت.

إن سلاسل params حساسة لحالة الأحرف ومن الأفضل توحيدها لضمان إنشاء المسارات بشكل صحيح. على سبيل المثال، إذا تم إرجاع WoRLD لمعلمة، فسوف تتطابق فقط إذا كان WoRLD هو المسار الذي تمت زيارته، وليس world أو World.

بشكل منفصل عن كائن params، يمكن إرجاع حقل locale عند تكوين i18n، والذي يحدد اللغة للمسار الذي يتم إنشاؤه.

fallback: false

إذا كانت fallback هي false، فإن أي مسارات لم يتم إرجاعها بواسطة getStaticPaths ستؤدي إلى صفحة 404.

عند تشغيل next build، سيتحقق Next.js مما إذا كانت getStaticPaths قد أرجعت fallback: false، ثم سيقوم ببناء فقط المسارات التي تم إرجاعها بواسطة getStaticPaths. هذا الخيار مفيد إذا كان لديك عدد صغير من المسارات لإنشائها، أو إذا لم تتم إضافة بيانات صفحات جديدة بشكل متكرر. إذا وجدت أنك بحاجة إلى إضافة المزيد من المسارات، وكان لديك fallback: false، فسوف تحتاج إلى تشغيل next build مرة أخرى حتى يمكن إنشاء المسارات الجديدة.

المثال التالي يحضر مسبقًا مقالة مدونة واحدة لكل صفحة تسمى pages/posts/[id].js. سيتم جلب قائمة مقالات المدونة من نظام إدارة المحتوى (CMS) وإرجاعها بواسطة getStaticPaths. ثم، لكل صفحة، يقوم بجلب بيانات المقالة من نظام إدارة المحتوى باستخدام getStaticProps.

pages/posts/[id].js
function Post({ post }) {
  // عرض المقالة...
}

// يتم استدعاء هذه الدالة أثناء وقت البناء
export async function getStaticPaths() {
  // استدعاء نقطة نهاية API خارجية للحصول على المقالات
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // الحصول على المسارات التي نريد تحضيرها مسبقًا بناءً على المقالات
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // سنقوم بتحضير هذه المسارات مسبقًا فقط أثناء وقت البناء.
  // { fallback: false } تعني أن المسارات الأخرى يجب أن تعرض 404.
  return { paths, fallback: false }
}

// يتم استدعاء هذا أيضًا أثناء وقت البناء
export async function getStaticProps({ params }) {
  // params يحتوي على `id` للمقالة.
  // إذا كان المسار مثل /posts/1، فإن params.id هو 1
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

  // تمرير بيانات المقالة إلى الصفحة عبر props
  return { props: { post } }
}

export default Post

fallback: true

أمثلة

إذا كانت fallback هي true، فإن سلوك getStaticProps يتغير بالطرق التالية:

  • سيتم تحويل المسارات التي تم إرجاعها من getStaticPaths إلى HTML أثناء وقت البناء بواسطة getStaticProps.
  • المسارات التي لم يتم إنشاؤها أثناء وقت البناء لن تؤدي إلى صفحة 404. بدلاً من ذلك، سيقوم Next.js بعرض نسخة "احتياطية" (fallback) من الصفحة عند الطلب الأول لمثل هذا المسار. لن يتم تقديم نسخة احتياطية لمحركات البحث مثل Google، وبدلاً من ذلك سيتصرف المسار كما في fallback: 'blocking'.
  • عند التنقل إلى صفحة بها fallback: true عبر next/link أو next/router (من جانب العميل)، لن يقدم Next.js نسخة احتياطية وبدلاً من ذلك ستصرف الصفحة كما في fallback: 'blocking'.
  • في الخلفية، سيقوم Next.js بإنشاء HTML و JSON للمسار المطلوب بشكل ثابت. وهذا يشمل تشغيل getStaticProps.
  • عند الانتهاء، يتلقى المتصفح JSON للمسار الذي تم إنشاؤه. سيتم استخدام هذا لعرض الصفحة تلقائيًا مع الخصائص المطلوبة. من وجهة نظر المستخدم، ستنتقل الصفحة من الصفحة الاحتياطية إلى الصفحة الكاملة.
  • في نفس الوقت، يضيف Next.js هذا المسار إلى قائمة الصفحات المحضرة مسبقًا. الطلبات اللاحقة لنفس المسار ستقدم الصفحة التي تم إنشاؤها، مثل الصفحات الأخرى المحضرة مسبقًا أثناء وقت البناء.

ملاحظة جيدة: لا يتم دعم fallback: true عند استخدام output: 'export'.

متى يكون fallback: true مفيدًا؟

fallback: true مفيد إذا كان تطبيقك يحتوي على عدد كبير جدًا من الصفحات الثابتة التي تعتمد على البيانات (مثل موقع تجارة إلكترونية كبير جدًا). إذا كنت تريد تحضير جميع صفحات المنتجات مسبقًا، فسوف تستغرق عمليات البناء وقتًا طويلاً جدًا.

بدلاً من ذلك، يمكنك إنشاء مجموعة فرعية صغيرة من الصفحات بشكل ثابت واستخدام fallback: true للباقي. عندما يطلب شخص ما صفحة لم يتم إنشاؤها بعد، سيرى المستخدم الصفحة مع مؤشر تحميل أو مكون هيكل عظمي.

بعد وقت قصير، ينتهي getStaticProps وسيتم عرض الصفحة بالبيانات المطلوبة. من الآن فصاعدًا، سيحصل كل من يطلب نفس الصفحة على الصفحة المحضرة مسبقًا بشكل ثابت.

هذا يضمن أن المستخدمين يحصلون دائمًا على تجربة سريعة مع الحفاظ على عمليات بناء سريعة وفوائد التوليد الثابت.

fallback: true لن يحدث الصفحات التي تم إنشاؤها، لذلك يمكنك الاطلاع على التجديد الثابت التدريجي (Incremental Static Regeneration) لذلك.

fallback: 'blocking'

إذا كانت fallback هي 'blocking'، فإن المسارات الجديدة التي لم يتم إرجاعها بواسطة getStaticPaths ستنتظر إنشاء HTML، مماثل لـ SSR (ولهذا السبب تسمى blocking)، ثم يتم تخزينها مؤقتًا للطلبات المستقبلية بحيث تحدث مرة واحدة فقط لكل مسار.

سيتصرف getStaticProps كما يلي:

  • سيتم تحويل المسارات التي تم إرجاعها من getStaticPaths إلى HTML أثناء وقت البناء بواسطة getStaticProps.
  • المسارات التي لم يتم إنشاؤها أثناء وقت البناء لن تؤدي إلى صفحة 404. بدلاً من ذلك، سيقوم Next.js بعرض SSR عند الطلب الأول وإرجاع HTML الذي تم إنشاؤه.
  • عند الانتهاء، يتلقى المتصفح HTML للمسار الذي تم إنشاؤه. من وجهة نظر المستخدم، سينتقل من "المتصفح يطلب الصفحة" إلى "تم تحميل الصفحة بالكامل". لا يوجد وميض لحالة التحميل/الاحتياطي.
  • في نفس الوقت، يضيف Next.js هذا المسار إلى قائمة الصفحات المحضرة مسبقًا. الطلبات اللاحقة لنفس المسار ستقدم الصفحة التي تم إنشاؤها، مثل الصفحات الأخرى المحضرة مسبقًا أثناء وقت البناء.

fallback: 'blocking' لن يحدث الصفحات التي تم إنشاؤها افتراضيًا. لتحديث الصفحات التي تم إنشاؤها، استخدم التجديد الثابت التدريجي (Incremental Static Regeneration) بالتزامن مع fallback: 'blocking'.

ملاحظة جيدة: لا يتم دعم fallback: 'blocking' عند استخدام output: 'export'.

الصفحات الاحتياطية (Fallback pages)

في النسخة "الاحتياطية" من الصفحة:

  • ستكون خصائص الصفحة فارغة.
  • باستخدام الموجه (router)، يمكنك اكتشاف ما إذا كان يتم عرض النسخة الاحتياطية، router.isFallback سيكون true.

المثال التالي يوضح استخدام isFallback:

pages/posts/[id].js
import { useRouter } from 'next/router'

function Post({ post }) {
  const router = useRouter()

  // إذا لم يتم إنشاء الصفحة بعد، سيتم عرض هذا
  // في البداية حتى ينتهي تشغيل getStaticProps()
  if (router.isFallback) {
    return <div>جار التحميل...</div>
  }

  // عرض المقالة...
}

// يتم استدعاء هذه الدالة أثناء وقت البناء
export async function getStaticPaths() {
  return {
    // فقط `/posts/1` و `/posts/2` تم إنشاؤهما أثناء وقت البناء
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    // تمكين إنشاء صفحات إضافية بشكل ثابت
    // على سبيل المثال: `/posts/3`
    fallback: true,
  }
}

// يتم استدعاء هذا أيضًا أثناء وقت البناء
export async function getStaticProps({ params }) {
  // params يحتوي على `id` للمقالة.
  // إذا كان المسار مثل /posts/1، فإن params.id هو 1
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

  // تمرير بيانات المقالة إلى الصفحة عبر props
  return {
    props: { post },
    // إعادة إنشاء المقالة مرة واحدة على الأكثر كل ثانية
    // إذا جاء طلب
    revalidate: 1,
  }
}

export default Post

سجل الإصدارات

الإصدارالتغييرات
v13.4.0أصبح موجه التطبيق (App Router) مستقرًا مع تبسيط جلب البيانات، بما في ذلك generateStaticParams()
v12.2.0أصبح التجديد الثابت التدريجي عند الطلب (On-Demand Incremental Static Regeneration) مستقرًا.
v12.1.0تمت إضافة التجديد الثابت التدريجي عند الطلب (On-Demand Incremental Static Regeneration) (بيتا).
v9.5.0أصبح التجديد الثابت التدريجي (Incremental Static Regeneration) مستقرًا
v9.3.0تم تقديم getStaticPaths.