دليل التبني التدريجي لموجه التطبيقات (App Router)

سيساعدك هذا الدليل في:

الترقية

إصدار Node.js

أصبح الحد الأدنى لإصدار Node.js الآن v18.17. راجع توثيق Node.js لمزيد من المعلومات.

إصدار Next.js

لتحديث Next.js إلى الإصدار 13، قم بتشغيل الأمر التالي باستخدام مدير الحزم المفضل لديك:

Terminal
npm install next@latest react@latest react-dom@latest

إصدار ESLint

إذا كنت تستخدم ESLint، فستحتاج إلى ترقية إصدار ESLint:

Terminal
npm install -D eslint-config-next@latest

معلومة مفيدة: قد تحتاج إلى إعادة تشغيل خادم ESLint في VS Code لتفعيل تغييرات ESLint. افتح لوحة الأوامر (cmd+shift+p على Mac؛ ctrl+shift+p على Windows) وابحث عن ESLint: Restart ESLint Server.

الخطوات التالية

بعد التحديث، راجع الأقسام التالية للخطوات التالية:

ترقية الميزات الجديدة

أدخل Next.js 13 موجه التطبيقات الجديد (App Router) بميزات واتفاقيات جديدة. يتوفر الموجه الجديد في دليل app ويتعايش مع دليل pages.

لا تتطلب الترقية إلى Next.js 13 استخدام موجه التطبيقات الجديد (App Router). يمكنك الاستمرار في استخدام pages مع الميزات الجديدة التي تعمل في كلا الدليلين، مثل مكون الصورة المحدث، مكون الرابط، مكون السكريبت، وتحسين الخطوط.

مكون <Image/>

قدم Next.js 12 تحسينات جديدة لمكون الصورة مع استيراد مؤقت: next/future/image. شملت هذه التحسينات كمية أقل من JavaScript على جانب العميل، طرق أسهل لتوسيع وتصميم الصور، تحسين إمكانية الوصول، وتحميل كسول أصلي في المتصفح.

في الإصدار 13، أصبح هذا السلوك الجديد هو الافتراضي لـ next/image.

هناك اثنان من أدوات التعديل التلقائي (codemods) لمساعدتك في الهجرة إلى مكون الصورة الجديد:

  • أداة next-image-to-legacy-image التلقائية: تقوم بإعادة تسمية استيرادات next/image إلى next/legacy/image تلقائياً وبأمان. ستحافظ المكونات الحالية على نفس السلوك.
  • أداة next-image-experimental التلقائية: تضيف أنماط مضمنة بشكل خطير وتزيل الخصائص غير المستخدمة. سيغير هذا سلوك المكونات الحالية لتتوافق مع الإعدادات الافتراضية الجديدة. لاستخدام هذه الأداة، يجب تشغيل أداة next-image-to-legacy-image أولاً.

لم يعد مكون <Link> يتطلب إضافة علامة <a> يدوياً كطفل. تمت إضافة هذا السلوك كخيار تجريبي في الإصدار 12.2 وأصبح الآن الافتراضي. في Next.js 13، يقوم <Link> دائماً بعرض <a> ويسمح لك بتمرير الخصائص إلى العلامة الأساسية.

على سبيل المثال:

import Link from 'next/link'

// Next.js 12: يجب تضمين `<a>` وإلا سيتم استبعادها
<Link href="/about">
  <a>About</a>
</Link>

// Next.js 13: يقوم `<Link>` دائماً بعرض `<a>` في الخلفية
<Link href="/about">
  About
</Link>

لترقية روابطك إلى Next.js 13، يمكنك استخدام أداة new-link التلقائية.

مكون <Script>

تم تحديث سلوك next/script لدعم كل من pages وapp، ولكن يجب إجراء بعض التغييرات لضمان هجرة سلسة:

  • انقل أي نصوص beforeInteractive التي أدرجتها سابقاً في _document.js إلى ملف التخطيط الجذري (app/layout.tsx).
  • لا تعمل استراتيجية worker التجريبية بعد في app وسيتعين إزالة النصوص المحددة بهذه الاستراتيجية أو تعديلها لاستخدام استراتيجية مختلفة (مثل lazyOnload).
  • لن تعمل معالجات onLoad وonReady وonError في مكونات الخادم (Server Components)، لذا تأكد من نقلها إلى مكون العميل (Client Component) أو إزالتها تماماً.

تحسين الخطوط

سابقاً، ساعدك Next.js في تحسين الخطوط عن طريق تضمين CSS للخطوط. يقدم الإصدار 13 وحدة next/font الجديدة التي تمنحك القدرة على تخصيص تجربة تحميل الخطوط مع ضمان أداء وخصوصية ممتازين. يعمل next/font في كل من دليل pages وapp.

بينما لا يزال تضمين CSS يعمل في pages، إلا أنه لا يعمل في app. يجب استخدام next/font بدلاً من ذلك.

راجع صفحة تحسين الخطوط لمعرفة كيفية استخدام next/font.

الهجرة من pages إلى app

🎥 شاهد: تعلم كيفية تبني موجه التطبيقات تدريجياً → YouTube (16 دقيقة).

قد تكون الانتقال إلى موجه التطبيقات هي المرة الأولى التي تستخدم فيها ميزات React التي يبني عليها Next.js مثل مكونات الخادم (Server Components) وSuspense وغيرها. عند الجمع مع ميزات Next.js الجديدة مثل الملفات الخاصة والتخطيطات، تعني الهجرة تعلم مفاهيم ونماذج عقلية وتغييرات سلوكية جديدة.

نوصي بتقليل التعقيد المرتبط بهذه التحديثات عن طريق تقسيم عملية الهجرة إلى خطوات أصغر. تم تصميم دليل app عمداً للعمل بالتزامن مع دليل pages للسماح بالهجرة صفحة بصفحة بشكل تدريجي.

  • يدعم دليل app المسارات المتداخلة والتخطيطات. تعلم المزيد.
  • استخدم المجلدات المتداخلة لتحديد المسارات وملف page.js الخاص لجعل جزء المسار متاحاً للجمهور. تعلم المزيد.
  • تُستخدم الاتفاقيات الخاصة للملفات لإنشاء واجهة مستخدم لكل جزء من المسار. أكثر الملفات الخاصة شيوعاً هي page.js وlayout.js.
    • استخدم page.js لتعريف واجهة المستخدم الفريدة للمسار.
    • استخدم layout.js لتعريف واجهة المستخدم المشتركة بين عدة مسارات.
    • يمكن استخدام امتدادات الملفات .js أو .jsx أو .tsx للملفات الخاصة.
  • يمكنك وضع ملفات أخرى في دليل app مثل المكونات والأنماط والاختبارات وغيرها. تعلم المزيد.
  • تم استبدال وظائف جلب البيانات مثل getServerSideProps وgetStaticProps بـ واجهة برمجة تطبيقات جديدة داخل app. تم استبدال getStaticPaths بـ generateStaticParams.
  • تم استبدال pages/_app.js وpages/_document.js بتخطيط جذري واحد app/layout.js. تعلم المزيد.
  • تم استبدال pages/_error.js بملفات خاصة error.js أكثر دقة. تعلم المزيد.
  • تم استبدال pages/404.js بملف not-found.js.
  • تم استبدال مسارات API pages/api/* بملف route.js الخاص (Route Handler).

الخطوة 1: إنشاء دليل app

قم بتحديث إلى أحدث إصدار من Next.js (يتطلب الإصدار 13.4 أو أحدث):

npm install next@latest

ثم، قم بإنشاء دليل app جديد في جذر مشروعك (أو دليل src/).

الخطوة 2: إنشاء تخطيط جذري

قم بإنشاء ملف app/layout.tsx جديد داخل دليل app. هذا هو التخطيط الجذري الذي سيتم تطبيقه على جميع المسارات داخل app.

export default function RootLayout({
  // يجب أن تقبل التخطيطات خاصية children.
  // سيتم ملؤها بالتخطيطات أو الصفحات المتداخلة
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
export default function RootLayout({
  // يجب أن تقبل التخطيطات خاصية children.
  // سيتم ملؤها بالتخطيطات أو الصفحات المتداخلة
  children,
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
  • يجب أن يتضمن دليل app تخطيطاً جذرياً.
  • يجب أن يحدد التخطيط الجذري علامات <html> و<body> لأن Next.js لا ينشئها تلقائياً.
  • يحل التخطيط الجذري محل ملفات pages/_app.tsx وpages/_document.tsx.
  • يمكن استخدام امتدادات الملفات .js أو .jsx أو .tsx لملفات التخطيط.

لإدارة عناصر HTML <head>، يمكنك استخدام دعم SEO المدمج:

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Home',
  description: 'Welcome to Next.js',
}
export const metadata = {
  title: 'Home',
  description: 'Welcome to Next.js',
}

هجرة _document.js و_app.js

إذا كان لديك ملف _app أو _document موجود، يمكنك نسخ المحتويات (مثل الأنماط العامة) إلى التخطيط الجذري (app/layout.tsx). لن تنطبق الأنماط في app/layout.tsx على pages/*. يجب الاحتفاظ بـ _app/_document أثناء الهجرة لمنع تعطل مسارات pages/*. بمجرد اكتمال الهجرة، يمكنك حذفها بأمان.

إذا كنت تستخدم أي موفري سياق React، فسيتعين نقلهم إلى مكون عميل (Client Component).

هجرة نمط getLayout() إلى التخطيطات (اختياري)

كان Next.js يوصي بإضافة خاصية إلى مكونات الصفحة لتحقيق تخطيطات لكل صفحة في دليل pages. يمكن استبدال هذا النمط بالدعم الأصلي لـالتخطيطات المتداخلة في دليل app.

راجع مثال قبل وبعد

قبل

components/DashboardLayout.js
export default function DashboardLayout({ children }) {
  return (
    <div>
      <h2>My Dashboard</h2>
      {children}
    </div>
  )
}
pages/dashboard/index.js
import DashboardLayout from '../components/DashboardLayout'

export default function Page() {
  return <p>My Page</p>
}

Page.getLayout = function getLayout(page) {
  return <DashboardLayout>{page}</DashboardLayout>
}

بعد

  • أزل خاصية Page.getLayout من pages/dashboard/index.js واتبع خطوات هجرة الصفحات إلى دليل app.

    app/dashboard/page.js
    export default function Page() {
      return <p>My Page</p>
    }
  • انقل محتويات DashboardLayout إلى مكون عميل (Client Component) جديد للاحتفاظ بسلوك دليل pages.

    app/dashboard/DashboardLayout.js
    'use client' // يجب أن تكون هذه التوجيه في أعلى الملف، قبل أي استيرادات.
    
    // هذا مكون عميل (Client Component)
    export default function DashboardLayout({ children }) {
      return (
        <div>
          <h2>My Dashboard</h2>
          {children}
        </div>
      )
    }
  • استورد DashboardLayout إلى ملف layout.js جديد داخل دليل app.

    app/dashboard/layout.js
    import DashboardLayout from './DashboardLayout'
    
    // هذا مكون خادم (Server Component)
    export default function Layout({ children }) {
      return <DashboardLayout>{children}</DashboardLayout>
    }
  • يمكنك نقل الأجزاء غير التفاعلية من DashboardLayout.js (مكون العميل) إلى layout.js (مكون الخادم) تدريجياً لتقليل كمية JavaScript للمكون التي ترسلها إلى العميل.

الخطوة 3: هجرة next/head

في دليل pages، يتم استخدام مكون React next/head لإدارة عناصر HTML <head> مثل title وmeta. في دليل app، تم استبدال next/head بـدعم SEO المدمج الجديد.

قبل:

import Head from 'next/head'

export default function Page() {
  return (
    <>
      <Head>
        <title>My page title</title>
      </Head>
    </>
  )
}
import Head from 'next/head'

export default function Page() {
  return (
    <>
      <Head>
        <title>My page title</title>
      </Head>
    </>
  )
}

بعد:

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'My Page Title',
}

export default function Page() {
  return '...'
}
export const metadata = {
  title: 'My Page Title',
}

export default function Page() {
  return '...'
}

راجع جميع خيارات البيانات الوصفية (metadata).

الخطوة 4: ترحيل الصفحات

نوصي بتقسيم ترحيل الصفحة إلى خطوتين رئيسيتين:

  • الخطوة 1: نقل مكون الصفحة المصدر افتراضيًا إلى مكون عميل جديد.
  • الخطوة 2: استيراد مكون العميل الجديد إلى ملف page.js جديد داخل دليل app.

معلومة مفيدة: هذا هو أسهل مسار للترحيل لأنه يشبه إلى حد كبير سلوك دليل pages.

الخطوة 1: إنشاء مكون عميل جديد

  • أنشئ ملفًا منفصلًا جديدًا داخل دليل app (مثل app/home-page.tsx أو ما شابه) يقوم بتصدير مكون عميل. لتحديد مكونات العميل، أضف توجيه 'use client' في أعلى الملف (قبل أي استيرادات).
    • على غرار موجه الصفحات (Pages Router)، هناك خطوة تحسين لتقديم مسبق لمكونات العميل إلى HTML ثابت عند تحميل الصفحة الأولي.
  • انقل مكون الصفحة المصدر افتراضيًا من pages/index.js إلى app/home-page.tsx.
'use client'

// هذا مكون عميل (مشابه للمكونات في دليل `pages`)
// يتلقى البيانات كخصائص، وله إمكانية الوصول إلى الحالة والتأثيرات، ويتم
// تقديمه مسبقًا على الخادم أثناء تحميل الصفحة الأولي.
export default function HomePage({ recentPosts }) {
  return (
    <div>
      {recentPosts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}
'use client'

// هذا مكون عميل. يتلقى البيانات كخصائص و
// لديه إمكانية الوصول إلى الحالة والتأثيرات تمامًا مثل مكونات الصفحة
// في دليل `pages`.
export default function HomePage({ recentPosts }) {
  return (
    <div>
      {recentPosts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}

الخطوة 2: إنشاء صفحة جديدة

  • أنشئ ملف app/page.tsx جديدًا داخل دليل app. هذا مكون خادم افتراضيًا.

  • استورد مكون العميل home-page.tsx إلى الصفحة.

  • إذا كنت تقوم بجلب البيانات في pages/index.js، انقل منطق جلب البيانات مباشرة إلى مكون الخادم باستخدام واجهات برمجة تطبيقات جلب البيانات الجديدة. راجع دليل ترقية جلب البيانات لمزيد من التفاصيل.

    // استورد مكون العميل الخاص بك
    import HomePage from './home-page'
    
    async function getPosts() {
      const res = await fetch('https://...')
      const posts = await res.json()
      return posts
    }
    
    export default async function Page() {
      // جلب البيانات مباشرة في مكون خادم
      const recentPosts = await getPosts()
      // إعادة توجيه البيانات التي تم جلبها إلى مكون العميل الخاص بك
      return <HomePage recentPosts={recentPosts} />
    }
    // استورد مكون العميل الخاص بك
    import HomePage from './home-page'
    
    async function getPosts() {
      const res = await fetch('https://...')
      const posts = await res.json()
      return posts
    }
    
    export default async function Page() {
      // جلب البيانات مباشرة في مكون خادم
      const recentPosts = await getPosts()
      // إعادة توجيه البيانات التي تم جلبها إلى مكون العميل الخاص بك
      return <HomePage recentPosts={recentPosts} />
    }
  • إذا كانت صفحتك السابقة تستخدم useRouter، فستحتاج إلى التحديث إلى خطافات التوجيه الجديدة. تعلم المزيد.

  • ابدأ خادم التطوير الخاص بك وقم بزيارة http://localhost:3000. يجب أن ترى مسار الفهرس الحالي الخاص بك، الذي يتم تقديمه الآن عبر دليل التطبيق.

الخطوة 5: ترحيل خطافات التوجيه

تمت إضافة موجه جديد لدعم السلوك الجديد في دليل app.

في app، يجب عليك استخدام الخطافات الثلاثة الجديدة المستوردة من next/navigation: useRouter()، usePathname()، و useSearchParams().

  • خطاف useRouter الجديد مستورد من next/navigation وله سلوك مختلف عن خطاف useRouter في pages المستورد من next/router.
  • خطاف useRouter الجديد لا يُرجع سلسلة pathname. استخدم خطاف usePathname المنفصل بدلاً من ذلك.
  • خطاف useRouter الجديد لا يُرجع كائن query. استخدم خطاف useSearchParams المنفصل بدلاً من ذلك.
  • يمكنك استخدام useSearchParams و usePathname معًا للاستماع إلى تغييرات الصفحة. راجع قسم أحداث الموجه (Router Events) لمزيد من التفاصيل.
  • هذه الخطافات الجديدة مدعومة فقط في مكونات العميل. لا يمكن استخدامها في مكونات الخادم.
'use client'

import { useRouter, usePathname, useSearchParams } from 'next/navigation'

export default function ExampleClientComponent() {
  const router = useRouter()
  const pathname = usePathname()
  const searchParams = useSearchParams()

  // ...
}
'use client'

import { useRouter, usePathname, useSearchParams } from 'next/navigation'

export default function ExampleClientComponent() {
  const router = useRouter()
  const pathname = usePathname()
  const searchParams = useSearchParams()

  // ...
}

بالإضافة إلى ذلك، يحتوي خطاف useRouter الجديد على التغييرات التالية:

  • تمت إزالة isFallback لأنه تم استبدال fallback.
  • تمت إزالة قيم locale و locales و defaultLocales و domainLocales لأن ميزات i18n المضمنة في Next.js لم تعد ضرورية في دليل app. تعلم المزيد حول i18n.
  • تمت إزالة basePath. البديل لن يكون جزءًا من useRouter. لم يتم تنفيذه بعد.
  • تمت إزالة asPath لأنه تمت إزالة مفهوم as من الموجه الجديد.
  • تمت إزالة isReady لأنه لم يعد ضروريًا. أثناء التقديم الثابت (static rendering)، أي مكون يستخدم خطاف useSearchParams() سيتخطى خطوة التقديم المسبق وسيتم تقديمه على العميل أثناء التشغيل.

عرض مرجع واجهة برمجة تطبيقات useRouter().

الخطوة 6: ترحيل طرق جلب البيانات

يستخدم دليل pages getServerSideProps و getStaticProps لجلب البيانات للصفحات. داخل دليل app، تم استبدال وظائف جلب البيانات السابقة هذه بـواجهة برمجة تطبيقات أبسط مبنية على fetch() و مكونات خادم React غير المتزامنة.

export default async function Page() {
  // يجب تخزين هذا الطلب مؤقتًا حتى يتم إبطاله يدويًا.
  // مشابه لـ `getStaticProps`.
  // `force-cache` هو الافتراضي ويمكن حذفه.
  const staticData = await fetch(`https://...`, { cache: 'force-cache' })

  // يجب إعادة جلب هذا الطلب في كل طلب.
  // مشابه لـ `getServerSideProps`.
  const dynamicData = await fetch(`https://...`, { cache: 'no-store' })

  // يجب تخزين هذا الطلب مؤقتًا لمدة 10 ثوانٍ.
  // مشابه لـ `getStaticProps` مع خيار `revalidate`.
  const revalidatedData = await fetch(`https://...`, {
    next: { revalidate: 10 },
  })

  return <div>...</div>
}
export default async function Page() {
  // يجب تخزين هذا الطلب مؤقتًا حتى يتم إبطاله يدويًا.
  // مشابه لـ `getStaticProps`.
  // `force-cache` هو الافتراضي ويمكن حذفه.
  const staticData = await fetch(`https://...`, { cache: 'force-cache' })

  // يجب إعادة جلب هذا الطلب في كل طلب.
  // مشابه لـ `getServerSideProps`.
  const dynamicData = await fetch(`https://...`, { cache: 'no-store' })

  // يجب تخزين هذا الطلب مؤقتًا لمدة 10 ثوانٍ.
  // مشابه لـ `getStaticProps` مع خيار `revalidate`.
  const revalidatedData = await fetch(`https://...`, {
    next: { revalidate: 10 },
  })

  return <div>...</div>
}

التقديم من جانب الخادم (getServerSideProps)

في دليل pages، يُستخدم getServerSideProps لجلب البيانات من الخادم وإعادة توجيه الخصائص إلى مكون React المصدر افتراضيًا في الملف. يتم تقديم HTML الأولي للصفحة من الخادم، يليه "ترطيب" الصفحة في المتصفح (جعلها تفاعلية).

pages/dashboard.js
// دليل `pages`

export async function getServerSideProps() {
  const res = await fetch(`https://...`)
  const projects = await res.json()

  return { props: { projects } }
}

export default function Dashboard({ projects }) {
  return (
    <ul>
      {projects.map((project) => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  )
}

في دليل app، يمكننا وضع جلب البيانات داخل مكونات React باستخدام مكونات الخادم (Server Components). هذا يسمح لنا بإرسال كمية أقل من JavaScript إلى العميل، مع الحفاظ على HTML المقدم من الخادم.

عن طريق تعيين خيار cache إلى no-store، يمكننا الإشارة إلى أن البيانات التي تم جلبها يجب ألا يتم تخزينها مؤقتًا أبدًا. هذا مشابه لـ getServerSideProps في دليل pages.

// دليل `app`

// يمكن تسمية هذه الوظيفة بأي شيء
async function getProjects() {
  const res = await fetch(`https://...`, { cache: 'no-store' })
  const projects = await res.json()

  return projects
}

export default async function Dashboard() {
  const projects = await getProjects()

  return (
    <ul>
      {projects.map((project) => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  )
}
// دليل `app`

// يمكن تسمية هذه الوظيفة بأي شيء
async function getProjects() {
  const res = await fetch(`https://...`, { cache: 'no-store' })
  const projects = await res.json()

  return projects
}

export default async function Dashboard() {
  const projects = await getProjects()

  return (
    <ul>
      {projects.map((project) => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  )
}

الوصول إلى كائن الطلب

في دليل pages، يمكنك استرداد البيانات المستندة إلى الطلب بناءً على واجهة برمجة تطبيقات HTTP لـ Node.js.

على سبيل المثال، يمكنك استرداد كائن req من getServerSideProps واستخدامه لاسترداد ملفات تعريف الارتباط (cookies) ورؤوس (headers) الطلب.

pages/index.js
// دليل `pages`

export async function getServerSideProps({ req, query }) {
  const authHeader = req.getHeaders()['authorization'];
  const theme = req.cookies['theme'];

  return { props: { ... }}
}

export default function Page(props) {
  return ...
}

يكشف دليل app عن وظائف جديدة للقراءة فقط لاسترداد بيانات الطلب:

// دليل `app`
import { cookies, headers } from 'next/headers'

async function getData() {
  const authHeader = headers().get('authorization')

  return '...'
}

export default async function Page() {
  // يمكنك استخدام `cookies()` أو `headers()` داخل مكونات الخادم
  // مباشرة أو في وظيفة جلب البيانات الخاصة بك
  const theme = cookies().get('theme')
  const data = await getData()
  return '...'
}
// دليل `app`
import { cookies, headers } from 'next/headers'

async function getData() {
  const authHeader = headers().get('authorization')

  return '...'
}

export default async function Page() {
  // يمكنك استخدام `cookies()` أو `headers()` داخل مكونات الخادم
  // مباشرة أو في وظيفة جلب البيانات الخاصة بك
  const theme = cookies().get('theme')
  const data = await getData()
  return '...'
}

توليد الموقع الثابت (getStaticProps)

في دليل pages، تُستخدم وظيفة getStaticProps لتقديم صفحة مسبقًا أثناء البناء. يمكن استخدام هذه الوظيفة لجلب البيانات من واجهة برمجة تطبيقات خارجية أو مباشرة من قاعدة بيانات، وإعادة توجيه هذه البيانات إلى الصفحة بأكملها أثناء إنشائها.

pages/index.js
// دليل `pages`

export async function getStaticProps() {
  const res = await fetch(`https://...`)
  const projects = await res.json()

  return { props: { projects } }
}

export default function Index({ projects }) {
  return projects.map((project) => <div>{project.name}</div>)
}

في دليل app، سيكون جلب البيانات باستخدام fetch() افتراضيًا إلى cache: 'force-cache'، والذي سيخزن بيانات الطلب مؤقتًا حتى يتم إبطالها يدويًا. هذا مشابه لـ getStaticProps في دليل pages.

app/page.js
// دليل `app`

// يمكن تسمية هذه الوظيفة بأي شيء
async function getProjects() {
  const res = await fetch(`https://...`)
  const projects = await res.json()

  return projects
}

export default async function Index() {
  const projects = await getProjects()

  return projects.map((project) => <div>{project.name}</div>)
}

المسارات الديناميكية (getStaticPaths)

في دليل pages، تُستخدم الدالة getStaticPaths لتحديد المسارات الديناميكية التي يجب تقديمها مسبقًا وقت البناء.

pages/posts/[id].js
// دليل `pages`
import PostLayout from '@/components/post-layout'

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
  }
}

export async function getStaticProps({ params }) {
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

  return { props: { post } }
}

export default function Post({ post }) {
  return <PostLayout post={post} />
}

في دليل app، يتم استبدال getStaticPaths بـ generateStaticParams.

تتصرف generateStaticParams بشكل مشابه لـ getStaticPaths، ولكن لديها واجهة برمجة تطبيقات مبسطة لإعادة معلمات المسار ويمكن استخدامها داخل التخطيطات. الشكل الذي تُعيده generateStaticParams هو مصفوفة من المقاطع بدلاً من مصفوفة من كائنات param متداخلة أو سلسلة من المسارات المحلولة.

app/posts/[id]/page.js
// دليل `app`
import PostLayout from '@/components/post-layout'

export async function generateStaticParams() {
  return [{ id: '1' }, { id: '2' }]
}

async function getPost(params) {
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

  return post
}

export default async function Post({ params }) {
  const post = await getPost(params)

  return <PostLayout post={post} />
}

استخدام الاسم generateStaticParams أكثر ملاءمة من getStaticPaths للنموذج الجديد في دليل app. تم استبدال البادئة get بـ generate الأكثر وصفية، والتي تناسب بشكل أفضل الآن بعد أن لم تعد getStaticProps و getServerSideProps ضرورية. تم استبدال اللاحقة Paths بـ Params، وهي أكثر ملاءمة للتوجيه المتداخل مع مقاطع ديناميكية متعددة.


استبدال fallback

في دليل pages، تُستخدم الخاصية fallback التي تُرجعها getStaticPaths لتحديد سلوك الصفحة التي لم يتم تقديمها مسبقًا وقت البناء. يمكن تعيين هذه الخاصية إلى true لعرض صفحة احتياطية أثناء إنشاء الصفحة، أو false لعرض صفحة 404، أو blocking لإنشاء الصفحة وقت الطلب.

pages/posts/[id].js
// دليل `pages`

export async function getStaticPaths() {
  return {
    paths: [],
    fallback: 'blocking'
  };
}

export async function getStaticProps({ params }) {
  ...
}

export default function Post({ post }) {
  return ...
}

في دليل app، تتحكم خاصية config.dynamicParams في كيفية التعامل مع المعلمات خارج generateStaticParams:

  • true: (الافتراضي) يتم إنشاء المقاطع الديناميكية غير المضمنة في generateStaticParams عند الطلب.
  • false: ستعيد المقاطع الديناميكية غير المضمنة في generateStaticParams خطأ 404.

هذا يحل محل خيار fallback: true | false | 'blocking' الخاص بـ getStaticPaths في دليل pages. لم يتم تضمين خيار fallback: 'blocking' في dynamicParams لأن الفرق بين 'blocking' و true ضئيل مع البث.

app/posts/[id]/page.js
// دليل `app`

export const dynamicParams = true;

export async function generateStaticParams() {
  return [...]
}

async function getPost(params) {
  ...
}

export default async function Post({ params }) {
  const post = await getPost(params);

  return ...
}

عند تعيين dynamicParams إلى true (الافتراضي)، عند طلب مقطع مسار لم يتم إنشاؤه، سيتم تقديمه من الخادم وتخزينه مؤقتًا.

التجديد الثابت التدريجي (getStaticProps مع revalidate)

في دليل pages، تسمح لك الدالة getStaticProps بإضافة حقل revalidate لإعادة إنشاء الصفحة تلقائيًا بعد فترة زمنية معينة.

pages/index.js
// دليل `pages`

export async function getStaticProps() {
  const res = await fetch(`https://.../posts`)
  const posts = await res.json()

  return {
    props: { posts },
    revalidate: 60,
  }
}

export default function Index({ posts }) {
  return (
    <Layout>
      <PostList posts={posts} />
    </Layout>
  )
}

في دليل app، يمكن لجلب البيانات باستخدام fetch() استخدام revalidate، والذي سيخزن الطلب مؤقتًا للعدد المحدد من الثواني.

app/page.js
// دليل `app`

async function getPosts() {
  const res = await fetch(`https://.../posts`, { next: { revalidate: 60 } })
  const data = await res.json()

  return data.posts
}

export default async function PostList() {
  const posts = await getPosts()

  return posts.map((post) => <div>{post.name}</div>)
}

مسارات API

تستمر مسارات API في العمل في دليل pages/api دون أي تغييرات. ومع ذلك، تم استبدالها بـ Route Handlers في دليل app.

تسمح Route Handlers بإنشاء معالجات طلب مخصصة لمسار معين باستخدام واجهات برمجة تطبيقات الويب Request و Response.

export async function GET(request: Request) {}
export async function GET(request) {}

معلومة جيدة: إذا كنت تستخدم مسارات API سابقًا لاستدعاء API خارجي من العميل، يمكنك الآن استخدام مكونات الخادم بدلاً من ذلك لجلب البيانات بأمان. تعلم المزيد حول جلب البيانات.

الخطوة 7: التنسيق

في دليل pages، تقتصر أنماط CSS العامة على pages/_app.js فقط. مع دليل app، تم رفع هذا القيد. يمكن إضافة الأنماط العامة إلى أي تخطيط أو صفحة أو مكون.

Tailwind CSS

إذا كنت تستخدم Tailwind CSS، فستحتاج إلى إضافة دليل app إلى ملف tailwind.config.js الخاص بك:

tailwind.config.js
module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx,mdx}', // <-- أضف هذا السطر
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
  ],
}

ستحتاج أيضًا إلى استيراد أنماطك العامة في ملف app/layout.js:

app/layout.js
import '../styles/globals.css'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

تعلم المزيد حول التنسيق باستخدام Tailwind CSS

Codemods

يوفر Next.js تحويلات Codemod للمساعدة في ترقية قاعدة التعليمات البرمجية عند إيقاف ميزة ما. راجع Codemods لمزيد من المعلومات.