المسارات المتوازية (Parallel Routes)

تتيح لك المسارات المتوازية (Parallel Routes) عرض صفحة أو أكثر في نفس التخطيط بشكل متزامن أو مشروط. وهي مفيدة للأقسام الديناميكية للغاية في التطبيق، مثل لوحات التحكم وتغذيات المواقع الاجتماعية.

على سبيل المثال، بالنظر إلى لوحة التحكم، يمكنك استخدام المسارات المتوازية لعرض صفحات team و analytics في نفس الوقت:

رسم توضيحي للمسارات المتوازية

الفتحات (Slots)

يتم إنشاء المسارات المتوازية باستخدام فتحات مسماة. يتم تعريف الفتحات باستخدام اصطلاح @folder. على سبيل المثال، بنية الملفات التالية تحدد فتحتين: @analytics و @team:

بنية نظام الملفات للمسارات المتوازية

يتم تمرير الفتحات كخصائص (props) إلى تخطيط الأب المشترك. بالنسبة للمثال أعلاه، المكون في app/layout.js يقبل الآن خصائص فتحات @analytics و @team، ويمكنه عرضها بالتوازي مع خاصية children:

export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  return (
    <>
      {children}
      {team}
      {analytics}
    </>
  )
}
export default function Layout({ children, team, analytics }) {
  return (
    <>
      {children}
      {team}
      {analytics}
    </>
  )
}

ومع ذلك، الفتحات ليست أجزاء مسار (route segments) ولا تؤثر على بنية URL. على سبيل المثال، بالنسبة لـ /@analytics/views، سيكون URL هو /views لأن @analytics فتحة.

معلومة مفيدة:

  • خاصية children هي فتحة ضمنية لا تحتاج إلى تعيينها لمجلد. هذا يعني أن app/page.js تعادل app/@children/page.js.

الحالة النشطة والتنقل

بشكل افتراضي، يتتبع Next.js الحالة النشطة (أو الصفحة الفرعية) لكل فتحة. ومع ذلك، المحتوى المعروض داخل الفتحة سيعتمد على نوع التنقل:

  • التنقل اللين (Soft Navigation): أثناء التنقل من جانب العميل، سيقوم Next.js بعمل عرض جزئي (partial render)، تغيير الصفحة الفرعية داخل الفتحة، مع الحفاظ على الصفحات الفرعية النشطة للفتحات الأخرى، حتى لو لم تتطابق مع URL الحالي.
  • التنقل الصعب (Hard Navigation): بعد تحميل الصفحة بالكامل (تحديث المتصفح)، لا يمكن لـ Next.js تحديد الحالة النشطة للفتحات التي لا تتطابق مع URL الحالي. بدلاً من ذلك، سيعرض ملف default.js للفتحات غير المتطابقة، أو 404 إذا لم يكن default.js موجودًا.

معلومة مفيدة:

  • تساعد صفحة 404 للمسارات غير المتطابقة على ضمان عدم عرض مسار متوازي على صفحة لم يكن مقصودًا لها.

default.js

يمكنك تحديد ملف default.js ليعرض كخيار احتياطي للفتحات غير المتطابقة أثناء التحميل الأولي أو إعادة تحميل الصفحة بالكامل.

ضع في الاعتبار بنية المجلد التالية. فتحة @team لديها صفحة /settings، ولكن @analytics لا تملكها.

المسارات المتوازية للمسارات غير المتطابقة

عند التنقل إلى /settings، ستعرض فتحة @team صفحة /settings مع الحفاظ على الصفحة النشطة حاليًا لفتحة @analytics.

عند التحديث، سيعرض Next.js ملف default.js لـ @analytics. إذا لم يكن default.js موجودًا، فسيتم عرض 404 بدلاً من ذلك.

بالإضافة إلى ذلك، نظرًا لأن children هي فتحة ضمنية، فأنت بحاجة أيضًا إلى إنشاء ملف default.js لعرض خيار احتياطي لـ children عندما لا يتمكن Next.js من استعادة الحالة النشطة للصفحة الأصلية.

useSelectedLayoutSegment(s)

كل من useSelectedLayoutSegment و useSelectedLayoutSegments يقبلان معلمة parallelRoutesKey، مما يسمح لك بقراءة جزء المسار النشط داخل فتحة.

'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

export default function Layout({ auth }: { auth: React.ReactNode }) {
  const loginSegment = useSelectedLayoutSegment('auth')
  // ...
}
'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

export default function Layout({ auth }) {
  const loginSegment = useSelectedLayoutSegment('auth')
  // ...
}

عندما ينتقل المستخدم إلى app/@auth/login (أو /login في شريط العناوين)، سيكون loginSegment مساويًا للسلسلة "login".

أمثلة

مسارات مشروطة

يمكنك استخدام المسارات المتوازية لعرض مسارات مشروطة بناءً على شروط معينة، مثل دور المستخدم. على سبيل المثال، لعرض صفحة لوحة تحكم مختلفة لدور /admin أو /user:

رسم توضيحي للمسارات المشروطة
import { checkUserRole } from '@/lib/auth'

export default function Layout({
  user,
  admin,
}: {
  user: React.ReactNode
  admin: React.ReactNode
}) {
  const role = checkUserRole()
  return <>{role === 'admin' ? admin : user}</>
}
import { checkUserRole } from '@/lib/auth'

export default function Layout({ user, admin }) {
  const role = checkUserRole()
  return <>{role === 'admin' ? admin : user}</>
}

مجموعات علامات التبويب

يمكنك إضافة layout داخل فتحة للسماح للمستخدمين بالتنقل في الفتحة بشكل مستقل. هذا مفيد لإنشاء علامات تبويب.

على سبيل المثال، فتحة @analytics لديها صفحتان فرعيتان: /page-views و /visitors.

فتحة التحليلات مع صفحتين فرعيتين وتخطيط

داخل @analytics، أنشئ ملف layout لمشاركة علامات التبويب بين الصفحتين:

import Link from 'next/link'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <nav>
        <Link href="/page-views">Page Views</Link>
        <Link href="/visitors">Visitors</Link>
      </nav>
      <div>{children}</div>
    </>
  )
}
import Link from 'next/link'

export default function Layout({ children }) {
  return (
    <>
      <nav>
        <Link href="/page-views">Page Views</Link>
        <Link href="/visitors">Visitors</Link>
      </nav>
      <div>{children}</div>
    </>
  )
}

النوافذ المنبثقة (Modals)

يمكن استخدام المسارات المتوازية مع مسارات الاعتراض (Intercepting Routes) لإنشاء نوافذ منبثقة. هذا يسمح لك بحل التحديات الشائعة عند بناء النوافذ المنبثقة، مثل:

  • جعل محتوى النافذة المنبثقة قابلاً للمشاركة عبر URL.
  • الحفاظ على السياق عند تحديث الصفحة، بدلاً من إغلاق النافذة المنبثقة.
  • إغلاق النافذة المنبثقة عند التنقل للخلف بدلاً من الانتقال إلى المسار السابق.
  • إعادة فتح النافذة المنبثقة عند التنقل للأمام.

ضع في الاعتبار نمط واجهة المستخدم التالي، حيث يمكن للمستخدم فتح نافذة منبثقة لتسجيل الدخول من تخطيط باستخدام التنقل من جانب العميل، أو الوصول إلى صفحة /login منفصلة:

رسم توضيحي للمسارات المتوازية للنافذة المنبثقة لتسجيل الدخول

لتنفيذ هذا النمط، ابدأ بإنشاء مسار /login الذي يعرض صفحة تسجيل الدخول الرئيسية.

رسم توضيحي للمسارات المتوازية لصفحة تسجيل الدخول المنبثقة
import { Login } from '@/app/ui/login'

export default function Page() {
  return <Login />
}
import { Login } from '@/app/ui/login'

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

ثم، داخل فتحة @auth، أضف ملف default.js الذي يُرجع null. هذا يضمن عدم عرض النافذة المنبثقة عندما لا تكون نشطة.

export default function Default() {
  return null
}
export default function Default() {
  return null
}

داخل فتحة @auth الخاصة بك، اعترض مسار /login عن طريق تحديث مجلد /(.)login. استورد مكون <Modal> وأطفاله إلى ملف /(.)login/page.tsx:

import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'

export default function Page() {
  return (
    <Modal>
      <Login />
    </Modal>
  )
}
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'

export default function Page() {
  return (
    <Modal>
      <Login />
    </Modal>
  )
}

معلومة مفيدة:

فتح النافذة المنبثقة

الآن، يمكنك الاستفادة من موجه Next.js لفتح وإغلاق النافذة المنبثقة. هذا يضمن تحديث URL بشكل صحيح عند فتح النافذة المنبثقة، وعند التنقل للخلف والأمام.

لفتح النافذة المنبثقة، مرر فتحة @auth كخاصية إلى تخطيط الأب واعرضها جنبًا إلى جنب مع خاصية children.

import Link from 'next/link'

export default function Layout({
  auth,
  children,
}: {
  auth: React.ReactNode
  children: React.ReactNode
}) {
  return (
    <>
      <nav>
        <Link href="/login">فتح النافذة المنبثقة</Link>
      </nav>
      <div>{auth}</div>
      <div>{children}</div>
    </>
  )
}
import Link from 'next/link'

export default function Layout({ auth, children }) {
  return (
    <>
      <nav>
        <Link href="/login">فتح النافذة المنبثقة</Link>
      </nav>
      <div>{auth}</div>
      <div>{children}</div>
    </>
  )
}

عندما ينقر المستخدم على <Link>، ستفتح النافذة المنبثقة بدلاً من الانتقال إلى صفحة /login. ومع ذلك، عند التحديث أو التحميل الأولي، سيتم نقل المستخدم إلى صفحة تسجيل الدخول الرئيسية عند الانتقال إلى /login.

إغلاق النافذة المنبثقة

يمكنك إغلاق النافذة المنبثقة عن طريق استدعاء router.back() أو باستخدام مكون Link.

'use client'

import { useRouter } from 'next/navigation'

export function Modal({ children }: { children: React.ReactNode }) {
  const router = useRouter()

  return (
    <>
      <button
        onClick={() => {
          router.back()
        }}
      >
        إغلاق النافذة المنبثقة
      </button>
      <div>{children}</div>
    </>
  )
}
'use client'

import { useRouter } from 'next/navigation'

export function Modal({ children }) {
  const router = useRouter()

  return (
    <>
      <button
        onClick={() => {
          router.back()
        }}
      >
        إغلاق النافذة المنبثقة
      </button>
      <div>{children}</div>
    </>
  )
}

عند استخدام مكون Link للتنقل بعيدًا عن صفحة لا يجب أن تعرض فتحة @auth بعد الآن، نستخدم مسار catch-all يُرجع null.

import Link from 'next/link'

export function Modal({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Link href="/">إغلاق النافذة المنبثقة</Link>
      <div>{children}</div>
    </>
  )
}
import Link from 'next/link'

export function Modal({ children }) {
  return (
    <>
      <Link href="/">إغلاق النافذة المنبثقة</Link>
      <div>{children}</div>
    </>
  )
}
export default function CatchAll() {
  return null
}
export default function CatchAll() {
  return null
}

معلومة مفيدة:

  • نستخدم مسار catch-all في فتحة @auth لإغلاق النافذة المنبثقة بسبب السلوك الموصوف في الحالة النشطة والتنقل. نظرًا لأن عمليات التنقل من جانب العميل إلى مسار لم يعد يتطابق مع الفتحة ستبقى مرئية، نحتاج إلى مطابقة الفتحة مع مسار يُرجع null لإغلاق النافذة المنبثقة.
  • يمكن أن تشمل الأمثلة الأخرى فتح نافذة منبثقة للصورة في معرض مع وجود صفحة مخصصة /photo/[id]، أو فتح عربة التسوق في نافذة منبثقة جانبية.
  • عرض مثال للنوافذ المنبثقة مع مسارات الاعتراض والمتوازية.

واجهات التحميل والخطأ

يمكن بث المسارات المتوازية بشكل مستقل، مما يسمح لك بتحديد حالات الخطأ والتحميل المستقلة لكل مسار:

تمكن المسارات المتوازية حالات الخطأ والتحميل المخصصة

راجع وثائق واجهة التحميل (Loading UI) و معالجة الأخطاء (Error Handling) لمزيد من المعلومات.