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

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

معلومة جيدة: fallback: 'blocking' غير مدعوم عند استخدام output: 'export'.

صفحات Fallback

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

  • ستكون props الصفحة فارغة.
  • باستخدام الموجه (router)، يمكنك اكتشاف ما إذا كان يتم عرض fallback، 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 يحتوي على معرف المنشور.
  // إذا كان المسار مثل /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.