Backالعودة إلى المدونة

طلب تعليقات (RFC) حول التخطيطات

مسارات متداخلة وتخطيطات، توجيه من جانب الخادم والعميل، ميزات React 18، ومصممة لمكونات الخادم.

يحدد هذا الطلب للتعليقات (RFC) أكبر تحديث لـ Next.js منذ إطلاقه في 2016:

  • التخطيطات المتداخلة: بناء تطبيقات معقدة بمسارات متداخلة.
  • مصممة لمكونات الخادم: مُحسنة لتنقل الشجرة الفرعية.
  • تحسين جلب البيانات: جلب البيانات في التخطيطات مع تجنب الشلالات.
  • استخدام ميزات React 18: البث، التحولات، و Suspense.
  • التوجيه من جانب الخادم والعميل: توجيه مركزي للخادم مع سلوك يشبه تطبيق الصفحة الواحدة (SPA).
  • قابلية التبني بنسبة 100% تدريجياً: لا توجد تغييرات كاسحة حتى تتمكن من التبني تدريجياً.
  • أنماط توجيه متقدمة: مسارات متوازية، مسارات متقاطعة، والمزيد.

سيتم بناء موجه Next.js الجديد على أساس ميزات React 18 التي تم إصدارها مؤخراً. نخطط لإدخال إعدادات افتراضية واتفاقيات تسمح لك بتبني هذه الميزات الجديدة بسهولة والاستفادة من المزايا التي توفرها.

العمل على هذا الطلب للتعليقات مستمر وسنعلن عندما تصبح الميزات الجديدة متاحة. لتقديم ملاحظاتك، انضم إلى النقاش على مناقشات GitHub.

جدول المحتويات

الهدف

لقد كنا نجمع ملاحظات المجتمع من GitHub وDiscord وReddit واستطلاع المطورين حول القيود الحالية للتوجيه في Next.js. وجدنا أن:

  • يمكن تحسين تجربة المطور في إنشاء التخطيطات. يجب أن يكون من السهل إنشاء تخطيطات يمكن تداخلها ومشاركتها عبر المسارات والحفاظ على حالتها أثناء التنقل.
  • العديد من تطبيقات Next.js هي لوحات تحكم أو وحدات تحكم، والتي ستستفيد من حلول التوجيه الأكثر تطوراً.

بينما عمل نظام التوجيه الحالي بشكل جيد منذ بداية Next.js، نريد تسهيل بناء تطبيقات ويب أكثر أداءً وغنية بالميزات للمطورين.

كمشرفين على الإطار، نريد أيضاً بناء نظام توجيه متوافق مع الإصدارات السابقة ويتوافق مع مستقبل React.

ملاحظة: استوحيت بعض اتفاقيات التوجيه من الموجه القائم على Relay في Meta، حيث تم تطوير بعض ميزات مكونات الخادم أصلاً، وكذلك موجهات جانب العميل مثل React Router وEmber.js. استوحيت اتفاقية ملف layout.js من العمل المنجز في SvelteKit. نود أيضاً أن نشكر كاسيدي لفتحها طلب تعليقات سابق حول التخطيطات.

المصطلحات

يقدم هذا الطلب للتعليقات اتفاقيات وبناء جملة جديد للتوجيه. المصطلحات مبنية على React والمصطلحات القياسية لمنصة الويب. خلال الطلب، ستشاهد هذه المصطلحات مرتبطة بتعريفاتها أدناه.

  • الشجرة: اتفاقية لتصور هيكل هرمي. على سبيل المثال، شجرة مكونات مع مكونات أب وأبناء، هيكل مجلد، إلخ.
  • الشجرة الفرعية: جزء من الشجرة، يبدأ من الجذر (الأول) وينتهي عند الأوراق (الأخير).

  • مسار URL: جزء من عنوان URL يأتي بعد النطاق.
  • مقطع URL: جزء من مسار URL محدد بشرطات مائلة.

كيف يعمل التوجيه حالياً

اليوم، يستخدم Next.js نظام الملفات لتعيين المجلدات والملفات الفردية في دليل الصفحات إلى مسارات يمكن الوصول إليها عبر عناوين URL. كل ملف صفحة يصدر مكون React وله مسار مرتبط بناءً على اسم الملف. على سبيل المثال:

تقديم دليل app

لضمان إمكانية تبني هذه التحسينات الجديدة تدريجياً وتجنب التغييرات الكاسحة، نقترح دليلاً جديداً يسمى app.

سيعمل دليل app جنباً إلى جنب مع دليل pages. يمكنك نقل أجزاء من تطبيقك تدريجياً إلى دليل app الجديد للاستفادة من الميزات الجديدة. للتوافق مع الإصدارات السابقة، سيظل سلوك دليل pages كما هو وسيستمر في الحصول على الدعم.

تحديد المسارات

يمكنك استخدام التسلسل الهرمي للمجلدات داخل app لتحديد المسارات. المسار هو مسار واحد من المجلدات المتداخلة، يتبع التسلسل الهرمي من مجلد الجذر وصولاً إلى مجلد الورقة النهائي.

على سبيل المثال، يمكنك إضافة مسار جديد /dashboard/settings عن طريق تداخل مجلدين جديدين في دليل app.

ملاحظة:

  • مع هذا النظام، ستستخدم المجلدات لتحديد المسارات، والملفات لتحديد واجهة المستخدم (مع اتفاقيات ملفات جديدة مثل layout.js، page.js، وفي الجزء الثاني من الطلب loading.js).
  • يسمح لك ذلك بوضع ملفات مشروعك الخاصة (مكونات واجهة المستخدم، ملفات الاختبار، القصص، إلخ) داخل دليل app. حالياً، هذا ممكن فقط مع تهيئة pageExtensions.

مقاطع المسار

يمثل كل مجلد في الشجرة الفرعية مقطع مسار. يتم تعيين كل مقطع مسار إلى مقطع مقابِل في مسار URL.

على سبيل المثال، يتكون مسار /dashboard/settings من 3 مقاطع:

  • مقطع الجذر /
  • مقطع dashboard
  • مقطع settings

ملاحظة: تم اختيار اسم مقطع المسار لمطابقة المصطلحات الحالية حول مسارات URL.

التخطيطات

اتفاقية ملف جديدة: layout.js

حتى الآن، استخدمنا المجلدات لتحديد مسارات تطبيقنا. لكن المجلدات الفارغة لا تفعل أي شيء بمفردها. دعونا نناقش كيف يمكنك تحديد واجهة المستخدم التي سيتم تقديمها لهذه المسارات باستخدام اتفاقيات ملفات جديدة.

التخطيط هو واجهة مستخدم مشتركة بين مقاطع المسار في شجرة فرعية. لا تؤثر التخطيطات على مسارات URL ولا يتم إعادة تقديمها (يتم الحفاظ على حالة React) عندما يتنقل المستخدم بين المقاطع الشقيقة.

يمكن تعريف التخطيط عن طريق تصدير مكون React افتراضي من ملف layout.js. يجب أن يقبل المكون خاصية children التي سيتم ملؤها بالمقاطع التي يحيط بها التخطيط.

هناك نوعان من التخطيطات:

  • تخطيط الجذر: ينطبق على جميع المسارات
  • التخطيط العادي: ينطبق على مسارات محددة

يمكنك تداخل تخطيطين أو أكثر معاً لتشكيل تخطيطات متداخلة.

تخطيط الجذر

يمكنك إنشاء تخطيط جذر ينطبق على جميع مسارات تطبيقك عن طريق إضافة ملف layout.js داخل مجلد app.

ملاحظة:

  • يحل تخطيط الجذر محل الحاجة إلى تطبيق مخصص (_app.js) ومستند مخصص (_document.js) لأنه ينطبق على جميع المسارات.
  • ستتمكن من استخدام تخطيط الجذر لتخصيص غلاف المستند الأولي (مثل علامات <html> و <body>).
  • ستتمكن من جلب البيانات داخل تخطيط الجذر (والتخطيطات الأخرى).

التخطيطات العادية

يمكنك أيضاً إنشاء تخطيط ينطبق فقط على جزء من تطبيقك عن طريق إضافة ملف layout.js داخل مجلد معين.

على سبيل المثال، يمكنك إنشاء ملف layout.js داخل مجلد dashboard الذي سينطبق فقط على مقاطع المسار داخل dashboard.

تداخل التخطيطات

التخطيطات متداخلة افتراضياً.

على سبيل المثال، إذا أردنا دمج التخطيطين أعلاه. سيتم تطبيق تخطيط الجذر (app/layout.js) على تخطيط dashboard، والذي سيتم تطبيقه أيضاً على جميع مقاطع المسار داخل dashboard/*.

الصفحات

اتفاقية ملف جديدة: page.js

الصفحة هي واجهة مستخدم فريدة لمقطع مسار. يمكنك إنشاء صفحة عن طريق إضافة ملف page.js داخل مجلد.

على سبيل المثال، لإنشاء صفحات لمسارات /dashboard/*، يمكنك إضافة ملف page.js داخل كل مجلد. عندما يزور المستخدم /dashboard/settings، سيقدم Next.js ملف page.js لمجلد settings ملفوفاً في أي تخطيطات موجودة أعلى الشجرة الفرعية.

يمكنك إنشاء ملف page.js مباشرة داخل مجلد dashboard لمطابقة مسار /dashboard. سيتم تطبيق تخطيط dashboard أيضاً على هذه الصفحة:

يتكون هذا المسار من مقطعين:

  • مقطع الجذر /
  • مقطع dashboard

ملاحظة:

  • لكي يكون المسار صالحاً، يجب أن يحتوي على صفحة في مقطع الورقة الخاص به. إذا لم يكن كذلك، فسيؤدي المسار إلى خطأ.

سلوك التخطيط والصفحة

  • يمكن استخدام امتدادات الملفات js|jsx|ts|tsx للصفحات والتخطيطات.
  • مكونات الصفحة هي التصدير الافتراضي لـ page.js.
  • مكونات التخطيط هي التصدير الافتراضي لـ layout.js.
  • يجب أن تقبل مكونات التخطيط خاصية children.

عند تقديم مكون التخطيط، سيتم ملء خاصية children بتخطيط فرعي (إذا كان موجوداً في الشجرة الفرعية أدناه) أو بصفحة.

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

مثال:

app/layout.js
// تخطيط الجذر
// - ينطبق على جميع المسارات
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Header />
        {children}
        <Footer />
      </body>
    </html>
  );
}
app/dashboard/layout.js
// التخطيط العادي
// - ينطبق على مقاطع المسار في app/dashboard/*
export default function DashboardLayout({ children }) {
  return (
    <>
      <DashboardSidebar />
      {children}
    </>
  );
}
app/dashboard/analytics/page.js
// مكون الصفحة
// - واجهة المستخدم لمقطع `app/dashboard/analytics`
// - تطابق مسار URL `acme.com/dashboard/analytics`
export default function AnalyticsPage() {
  return <main>...</main>;
}

سيؤدي المزيج أعلاه من التخطيطات والصفحات إلى تقديم التسلسل الهرمي للمكونات التالي:

التسلسل الهرمي للمكونات
<RootLayout>
  <Header />
  <DashboardLayout>
    <DashboardSidebar />
    <AnalyticsPage>
      <main>...</main>
    </AnalyticsPage>
  </DashboardLayout>
  <Footer />
</RootLayout>

مكونات خادم React

ملاحظة: قدمت React أنواع مكونات جديدة: الخادم، العميل (مكونات React التقليدية)، والمشتركة. لمعرفة المزيد عن هذه الأنواع الجديدة، نوصي بقراءة طلب تعليقات مكونات الخادم في React.

مع هذا الطلب للتعليقات، يمكنك البدء في استخدام ميزات React وتبني مكونات خادم React تدريجياً في تطبيق Next.js الخاص بك.

ستستفيد البنية الداخلية لنظام التوجيه الجديد أيضاً من ميزات React التي تم إصدارها مؤخراً مثل البث، التحولات، و Suspense. هذه هي اللبنات الأساسية لمكونات خادم React.

مكونات الخادم كإعداد افتراضي

أحد أكبر التغييرات بين أدلة pages و app هو أنه افتراضياً، سيتم تقديم الملفات داخل app على الخادم كمكونات خادم React.

هذا سيسمح لك بتبني مكونات خادم React تلقائياً عند الانتقال من pages إلى app.

ملاحظة: يمكن استخدام مكونات الخادم في مجلد app أو مجلداتك الخاصة، ولكن لا يمكن استخدامها في دليل pages للتوافق مع الإصدارات السابقة.

اتفاقية مكونات العميل والخادم

سيتم دعم مكونات الخادم والعميل والمشتركة في مجلد app، وسيكون بإمكانك دمج هذه المكونات في شجرة.

هناك نقاش مستمر حول الاتفاقية الدقيقة لتحديد مكونات العميل (Client Components) ومكونات الخادم (Server Components). سنتبع قرار هذا النقاش.

  • حالياً، يمكن تعريف مكونات الخادم بإضافة .server.js إلى اسم الملف. مثلاً: layout.server.js
  • يمكن تعريف مكونات العميل بإضافة .client.js إلى اسم الملف. مثلاً: page.client.js.
  • تعتبر الملفات .js مكونات مشتركة. نظراً لأنها يمكن أن تُعرض على الخادم والعميل، يجب أن تحترم قيود كل سياق.

ملاحظة:

  • لمكونات العميل والخادم قيود يجب احترامها. عند اتخاذ قرار باستخدام مكون عميل أو خادم، نوصي باستخدام مكونات الخادم (الافتراضية) حتى تحتاج إلى استخدام مكون عميل.

الخطافات (Hooks)

سنضيف خطافات لمكونات العميل والخادم تسمح لك بالوصول إلى كائن الرؤوس (headers)، ملفات تعريف الارتباط (cookies)، أسماء المسارات (pathnames)، معلمات البحث (search params)، إلخ. في المستقبل، سنقدم وثائق تحتوي على مزيد من المعلومات.

بيئات التصيير (Rendering Environments)

سيكون لديك تحكم دقيق في المكونات التي ستكون في حزمة جافا سكريبت من جانب العميل باستخدام اتفاقية مكونات العميل والخادم.

افتراضياً، ستستخدم المسارات في app التوليد الثابت (Static Generation)، وسيتم التبديل إلى التصيير الديناميكي عندما يستخدم جزء من المسار خطافات من جانب الخادم التي تتطلب سياق طلب.

دمج مكونات العميل والخادم في مسار

في React، هناك قيود حول استيراد مكونات الخادم داخل مكونات العميل لأن مكونات الخادم قد تحتوي على كود خاص بالخادم فقط (مثل أدوات قاعدة البيانات أو نظام الملفات).

على سبيل المثال، استيراد مكون الخادم لن يعمل:

ClientComponent.js
import ServerComponent from './ServerComponent.js';
 
export default function ClientComponent() {
  return (
    <>
      <ServerComponent />
    </>
  );
}

ومع ذلك، يمكن تمرير مكون الخادم كطفل لمكون العميل. يمكنك القيام بذلك عن طريق تغليفها في مكون خادم آخر. مثلاً:

ClientComponent.js
export default function ClientComponent({ children }) {
  return (
    <>
      <h1>مكون العميل</h1>
      {children}
    </>
  );
}
 
// ServerComponent.js
export default function ServerComponent() {
  return (
    <>
      <h1>مكون الخادم</h1>
    </>
  );
}
 
// page.js
// من الممكن استيراد مكونات العميل والخادم داخل مكونات الخادم
// لأن هذا المكون يُصيَّر على الخادم
import ClientComponent from "./ClientComponent.js";
import ServerComponent from "./ServerComponent.js";
 
export default function ServerComponentPage() {
  return (
    <>
      <ClientComponent>
        <ServerComponent />
      </ClientComponent>
    </>
  );
}

مع هذا النمط، ستعرف React أنها بحاجة إلى تصيير ServerComponent على الخادم قبل إرسال النتيجة (التي لا تحتوي على أي كود خاص بالخادم) إلى العميل. من وجهة نظر مكون العميل، سيكون طفله قد تم تصييره بالفعل.

في التخطيطات (layouts)، يتم تطبيق هذا النمط مع خاصية children حتى لا تضطر إلى إنشاء مكون تغليف إضافي.

على سبيل المثال، سيستقبل مكون ClientLayout مكون ServerPage كطفل:

app/dashboard/layout.js
// تخطيط Dashboard هو مكون عميل
export default function ClientLayout({ children }) {
  // يمكن استخدام useState / useEffect هنا
  return (
    <>
      <h1>التخطيط</h1>
      {children}
    </>
  );
}
 
// الصفحة هي مكون خادم سيتم تمريره إلى تخطيط Dashboard
// app/dashboard/settings/page.js
export default function ServerPage() {
  return (
    <>
      <h1>الصفحة</h1>
    </>
  );
}

ملاحظة: هذا النمط من التركيب هو نمط مهم لتصيير مكونات الخادم داخل مكونات العميل. وهو يحدد سابقة لنمط واحد للتعلم، وهو أحد الأسباب التي جعلتنا نقرر استخدام خاصية children.

جلب البيانات (Data fetching)

سيكون من الممكن جلب البيانات داخل أجزاء متعددة في مسار. هذا يختلف عن دليل pages، حيث كان جلب البيانات محدوداً بمستوى الصفحة.

جلب البيانات في التخطيطات

يمكنك جلب البيانات في ملف layout.js باستخدام طرق جلب البيانات في Next.js مثل getStaticProps أو getServerSideProps.

على سبيل المثال، يمكن لتخطيط مدونة استخدام getStaticProps لجلب التصنيفات من نظام إدارة المحتوى (CMS)، والتي يمكن استخدامها لملء مكون جانبي:

app/blog/layout.js
export async function getStaticProps() {
  const categories = await getCategoriesFromCMS();
 
  return {
    props: { categories },
  };
}
 
export default function BlogLayout({ categories, children }) {
  return (
    <>
      <BlogSidebar categories={categories} />
      {children}
    </>
  );
}

طرق متعددة لجلب البيانات في مسار

يمكنك أيضاً جلب البيانات في أجزاء متعددة من المسار. على سبيل المثال، يمكن لتخطيط يجلب البيانات أن يلف صفحة تجلب بياناتها الخاصة.

باستخدام مثال المدونة أعلاه، يمكن لصفحة منشور واحدة استخدام getStaticProps و getStaticPaths لجلب بيانات المنشور من نظام إدارة المحتوى:

app/blog/[slug]/page.js
export async function getStaticPaths() {
  const posts = await getPostSlugsFromCMS();
 
  return {
    paths: posts.map((post) => ({
      params: { slug: post.slug },
    })),
  };
}
 
export async function getStaticProps({ params }) {
  const post = await getPostFromCMS(params.slug);
 
  return {
    props: { post },
  };
}
 
export default function BlogPostPage({ post }) {
  return <Post post={post} />;
}

بما أن كل من app/blog/layout.js و app/blog/[slug]/page.js يستخدمان getStaticProps، فإن Next.js سيولد ثابتاً مسار /blog/[slug] بالكامل كـ مكونات خادم React في وقت البناء - مما يؤدي إلى تقليل جافا سكريبت من جانب العميل وترطيب أسرع.

تحسن المسارات المولدة ثابتاً هذا الأمر أكثر، حيث يعيد التنقل من جانب العميل استخدام ذاكرة التخزين المؤقت (بيانات مكونات الخادم) ولا يعيد حساب العمل، مما يؤدي إلى تقليل وقت وحدة المعالجة المركزية لأنك تصير لقطة من مكونات الخادم.

السلوك والأولوية

يمكن استخدام طرق جلب البيانات في Next.js (getServerSideProps و getStaticProps) فقط في مكونات الخادم في مجلد app. تؤثر طرق جلب البيانات المختلفة في الأجزاء عبر مسار واحد على بعضها البعض.

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

استخدام getStaticProps مع إعادة التحقق (ISR) في جزء واحد سيؤثر على getStaticProps مع revalidate في الأجزاء الأخرى. إذا كان هناك فترتا إعادة تحقق في مسار واحد، فإن فترة إعادة التحقق الأقصر ستكون لها الأولوية.

ملاحظة: في المستقبل، قد يتم تحسين هذا للسماح بدقة كاملة في جلب البيانات في مسار.

جلب البيانات مع مكونات خادم React

مزيج التوجيه من جانب الخادم، مكونات خادم React، Suspense والتدفق له بعض الآثار على جلب البيانات والتصيير في Next.js:

جلب البيانات المتوازي

سيبدأ Next.js جلب البيانات بحماس بشكل متوازي لتقليل الشلالات. على سبيل المثال، إذا كان جلب البيانات متسلسلاً، فلا يمكن لكل جزء متداخل في المسار أن يبدأ جلب البيانات حتى يكتمل الجزء السابق. ولكن مع الجلب المتوازي، يمكن لكل جزء أن يبدأ جلب البيانات بحماس في نفس الوقت.

نظراً لأن التصيير قد يعتمد على السياق، سيبدأ تصيير كل جزء بمجرد جلب بياناته وانتهاء تصيير الوالد.

في المستقبل، مع Suspense، يمكن أن يبدأ التصيير على الفور - حتى لو لم تكتمل البيانات. إذا تم قراءة البيانات قبل توفرها، سيتم تشغيل Suspense. سيبدأ React في تصيير مكونات الخادم بتفاؤل، قبل اكتمال الطلبات، وسيضع النتيجة عند حل الطلبات.

الجلب والتصيير الجزئي

عند التنقل بين أجزاء المسار الشقيقة، سيجلب Next.js ويصير فقط من ذلك الجزء للأسفل. لن يحتاج إلى إعادة جلب أو تصيير أي شيء أعلى. هذا يعني في صفحة تشترك في تخطيط، سيتم الحفاظ على التخطيط عندما ينتقل المستخدم بين الصفحات الشقيقة، وسيجلب Next.js ويصير فقط من ذلك الجزء للأسفل.

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

على سبيل المثال، إذا انتقل المستخدم بين صفحات /analytics و /settings، سيعيد React تصيير أجزاء الصفحة ولكن سيحافظ على التخطيطات:

ملاحظة: سيكون من الممكن إجبار إعادة جلب البيانات أعلى الشجرة. ما زلنا نناقش تفاصيل كيف سيبدو هذا وسنقوم بتحديث RFC.

مجموعات المسار (Route Groups)

تتعاقب مجلد app مباشرة مع مسارات URL. ولكن من الممكن الخروج من هذا النمط عن طريق إنشاء مجموعة مسار. يمكن استخدام مجموعات المسار لـ:

  • تنظيم المسارات دون التأثير على بنية URL.
  • إخراج جزء مسار من تخطيط.
  • إنشاء تخطيطات جذر متعددة عن طريق تقسيم التطبيق.

الاتفاقية

يمكن إنشاء مجموعة مسار عن طريق تغليف اسم المجلد بين قوسين: (folderName)

ملاحظة: تسمية مجموعات المسار هي لأغراض تنظيمية فقط لأنها لا تؤثر على مسار URL.

مثال: إخراج مسار من تخطيط

لإخراج مسار من تخطيط، أنشئ مجموعة مسار جديدة (مثلاً (shop)) وانقل المسارات التي تشترك في نفس التخطيط إلى المجموعة (مثلاً account و cart). لن تشارك المسارات خارج المجموعة في التخطيط (مثلاً checkout).

قبل:

بعد:

مثال: تنظيم المسارات دون التأثير على مسار URL

بالمثل، لتنظيم المسارات، أنشئ مجموعة لإبقاء المسارات ذات الصلة معاً. سيتم حذف المجلدات بين القوسين من URL (مثلاً (marketing) أو (shop)).

مثال: إنشاء تخطيطات جذر متعددة

لإنشاء تخطيطات جذر متعددة، أنشئ مجموعتين أو أكثر من مجموعات المسار في المستوى الأعلى من دليل app. هذا مفيف لتقسيم تطبيق إلى أقسام لها واجهة مستخدم أو تجربة مختلفة تماماً. يمكن تخصيص علامات <html>, <body> و <head> لكل تخطيط جذر بشكل منفصل.

التوجيه المركزي للخادم (Server-Centric Routing)

حالياً، يستخدم Next.js التوجيه من جانب العميل. بعد التحميل الأولي وعند التنقل اللاحق، يتم إرسال طلب إلى الخادم لموارد الصفحة الجديدة. وهذا يشمل جافا سكريبت لكل مكون (بما في ذلك المكونات التي تظهر فقط تحت ظروف معينة) وخصائصها (بيانات JSON من getServerSideProps أو getStaticProps). بمجرد تحميل كل من جافا سكريبت والبيانات من الخادم، تصير React المكونات من جانب العميل.

في هذا النموذج الجديد، سيستخدم Next.js التوجيه المركزي للخادم مع الحفاظ على التحولات من جانب العميل. هذا يتوافق مع مكونات الخادم التي يتم تقييمها على الخادم.

عند التنقل، يتم جلب البيانات وتصير React المكونات من جانب الخادم. مخرجات الخادم هي تعليمات خاصة (ليست HTML أو JSON) لـ React على العميل لتحديث DOM. تحتوي هذه التعليمات على نتيجة مكونات الخادم المصيرة مما يعني أنه لا يجب تحميل جافا سكريبت لهذا المكون في المتصفح لتصيير النتيجة.

هذا على عكس الوضع الافتراضي الحالي لمكونات العميل، حيث يتم إرسال جافا سكريبت المكون إلى المتصفح ليتم تصييره من جانب العميل.

بعض فوائد التوجيه المركزي للخادم مع مكونات خادم React تشمل:

  • يستخدم التوجيه نفس الطلب المستخدم لمكونات الخادم (لا يتم إجراء طلبات خادم إضافية).
  • يتم تنفيذ عمل أقل على الخادم لأن التنقل بين المسارات يجلب ويصير فقط الأجزاء التي تتغير.
  • لا يتم تحميل جافا سكريبت إضافية في المتصفح عند التنقل من جانب العميل عندما لا يتم استخدام مكونات عميل جديدة.
  • يستفيد الموجه من بروتوكول تدفق جديد بحيث يمكن أن يبدأ التصيير قبل تحميل جميع البيانات.

عند تنقل المستخدمين حول التطبيق، سيخزن الموجه نتيجة حمل مكون خادم React في ذاكرة تخزين مؤقت من جانب العميل. يتم تقسيم ذاكرة التخزين المؤقت حسب أجزاء المسار مما يسمح بإبطال أي مستوى ويضمن الاتساق عبر التصييرات المتزامنة. هذا يعني أنه في بعض الحالات، يمكن إعادة استخدام ذاكرة التخزين المؤقت لجزء مسار تم جلبها مسبقاً.

ملاحظة

  • يمكن استخدام التوليد الثابت والتخزين المؤقت من جانب الخادم لتحسين جلب البيانات.
  • المعلومات أعلاه تصف سلوك التنقلات اللاحقة. التحميل الأولي هو عملية مختلفة تتضمن تصيير من جانب الخادم لإنشاء HTML.
  • بينما عمل التوجيه من جانب العميل بشكل جيد لـ Next.js، فإنه يتوسع بشكل سيء عندما يكون عدد المسارات المحتملة كبيراً لأن العميل يجب أن يقوم بتحميل خريطة مسار.
  • بشكل عام، باستخدام مكونات خادم React، يكون التنقل من جانب العميل أسرع لأننا نحمل ونصير عدداً أقل من المكونات في المتصفح.

حالات التحميل الفوري (Instant Loading States)

مع التوجيه من جانب الخادم، يحدث التنقل بعد جلب البيانات والتصيير لذلك من المهم عرض واجهة مستخدم تحميل أثناء جلب البيانات وإلا سيبدو التطبيق غير مستجيب.

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

أثناء التصيير:

  • سيكون التنقل إلى المسار الجديد فورياً.
  • ستبقى التخطيطات المشتركة قابلة للتفاعل أثناء تحميل أجزاء المسار الجديدة.
  • سيكون التنقل قابلاً للمقاطعة - مما يعني أن المستخدم يمكنه التنقل بين المسارات أثناء تحميل محتوى أحد المسارات.

هياكل التحميل الافتراضية

سيتم التعامل مع حدود Suspense تلقائيًا خلف الكواليس باستخدام اصطلاح ملف جديد يُسمى loading.js.

مثال:

ستتمكن من إنشاء هيكل تحميل افتراضي عن طريق إضافة ملف loading.js داخل مجلد.

يجب أن يقوم ملف loading.js بتصدير مكون React:

loading.js
export default function Loading() {
  return <YourSkeleton />
}
 
// layout.js
export default function Layout({children}) {
  return (
    <>
      <Sidebar />
      {children}
    </>
  )
}
 
// Output
<>
  <Sidebar />
  <Suspense fallback={<Loading />}>{children}</Suspense>
</>

سيؤدي هذا إلى تغليف جميع الأجزاء داخل المجلد بحد Suspense. سيتم استخدام الهيكل الافتراضي عند تحميل التخطيط لأول مرة وعند التنقل بين الصفحات الشقيقة.

معالجة الأخطاء

حدود الأخطاء هي مكونات React تلتقط الأخطاء البرمجية في أي مكان في شجرة المكونات الفرعية.

الاصطلاح

ستتمكن من إنشاء حد أخطاء يلتقط الأخطاء داخل شجرة فرعية عن طريق إضافة ملف error.js وتصدير مكون React افتراضي.

سيتم عرض المكون كحل بديل في حالة حدوث خطأ داخل تلك الشجرة الفرعية. يمكن استخدام هذا المكون لتسجيل الأخطاء وعرض معلومات مفيدة عن الخطأ وإضافة وظائف لمحاولة التعافي من الخطأ.

بسبب الطبيعة المتداخلة للأجزاء والتخطيطات، فإن إنشاء حدود الأخطاء يسمح لك بعزل الأخطاء لتلك الأجزاء من واجهة المستخدم. أثناء حدوث خطأ، ستظل التخطيطات فوق الحد قابلة للتفاعل وسيتم الحفاظ على حالتها.

error.js
export default function Error({ error, reset }) {
  return (
    <>
      حدث خطأ: {error.message}
      <button onClick={() => reset()}>حاول مرة أخرى</button>
    </>
  );
}
 
// layout.js
export default function Layout({children}) {
  return (
    <>
      <Sidebar />
      {children}
    </>
  )
}
 
// Output
<>
  <Sidebar />
  <ErrorBoundary fallback={<Error />}>{children}</ErrorBoundary>
</>

ملاحظة:

  • لن يتم التقاط الأخطاء داخل ملف layout.js في نفس الجزء الذي يحتوي على ملف error.js لأن حد الأخطاء التلقائي يغلف الأبناء للتخطيط وليس التخطيط نفسه.

القوالب

تتشابه القوالب مع التخطيطات في أنها تغلف كل تخطيط أو صفحة فرعية.

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

ملاحظة: نوصي باستخدام التخطيطات إلا إذا كان لديك سبب محدد لاستخدام قالب.

الاصطلاح

يمكن تعريف القالب عن طريق تصدير مكون React افتراضي من ملف template.js. يجب أن يقبل المكون خاصية children التي سيتم ملؤها بالأجزاء المتداخلة.

مثال

template.js
export default function Template({ children }) {
  return <Container>{children}</Container>;
}

سيكون الناتج المعروض لجزء مسار يحتوي على تخطيط و قالب كما يلي:

<Layout>
  {/* لاحظ أن القالب يحتوي على مفتاح فريد. */}
  <Template key={routeParam}>{children}</Template>
</Layout>

السلوك

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

  • تأثيرات الدخول/الخروج باستخدام CSS أو مكتبات الرسوم المتحركة
  • ميزات تعتمد على useEffect (مثل تسجيل مشاهدات الصفحة) و useState (مثل نموذج ملاحظات لكل صفحة)
  • لتغيير سلوك الإطار الافتراضي. على سبيل المثال، حدود Suspense داخل التخطيطات تظهر الحل البديل فقط عند تحميل التخطيط لأول مرة وليس عند تبديل الصفحات. بالنسبة للقوالب، يظهر الحل البديل في كل تنقل.

على سبيل المثال، ضع في اعتبارك تصميم تخطيط متداخل مع حاوية محددة يجب أن تغلف كل صفحة فرعية.

يمكنك وضع الحاوية داخل التخطيط الأب (shop/layout.js):

shop/layout.js
export default function Layout({ children }) {
  return <div className="container">{children}</div>;
}
 
// shop/page.js
export default function Page() {
  return <div>...</div>;
}
 
// shop/categories/layout.js
export default function CategoryLayout({ children }) {
  return <div>{children}</div>;
}

ولكن لن تعمل أي تأثيرات دخول/خروج عند تبديل الصفحات لأن التخطيط الأب المشترك لا يعيد التصيير.

يمكنك وضع الحاوية في كل تخطيط متداخل أو صفحة:

shop/layout.js
export default function Layout({ children }) {
  return <div>{children}</div>;
}
 
// shop/page.js
export default function Page() {
  return <div className="container">...</div>;
}
 
// shop/categories/layout.js
export default function CategoryLayout({ children }) {
  return <div className="container">{children}</div>;
}

ولكن بعد ذلك سيتعين عليك وضعها يدويًا في كل تخطيط متداخل أو صفحة مما قد يكون مملًا وعرضة للخطأ في التطبيقات الأكثر تعقيدًا.

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

أنماط التوجيه المتقدمة

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

اعتراض المسارات

في بعض الأحيان، قد يكون من المفيد اعتراض أجزاء المسار من داخل مسارات أخرى. عند التنقل، سيتم تحديث URL كالمعتاد، ولكن سيتم عرض الجزء المعترض داخل تخطيط المسار الحالي.

مثال

قبل: يؤدي النقر على الصورة إلى مسار جديد بتخطيطه الخاص.

بعد: من خلال اعتراض المسار، يؤدي النقر على الصورة الآن إلى تحميل الجزء داخل تخطيط المسار الحالي. على سبيل المثال، كنافذة منبثقة.

لاعتراض مسار /photo/[id] من داخل جزء /[username]، أنشئ مجلدًا مكررًا /photo/[id] داخل مجلد /[username]، وابدأه باصطلاح (..).

الاصطلاح

  • (..) - سيطابق جزء المسار بمستوى أعلى (شقيق للمجلد الأب). مشابه لـ ../ في المسارات النسبية.
  • (..)(..) - سيطابق جزء المسار بمستويين أعلى. مشابه لـ ../../ في المسارات النسبية.
  • (...) - سيطابق جزء المسار في المجلد الجذري.

ملاحظة: سيؤدي التحديث أو مشاركة الصفحة إلى تحميل المسار بتخطيطه الافتراضي.

مسارات متوازية ديناميكية

في بعض الأحيان، قد يكون من المفيد عرض جزأين أو أكثر من الأوراق (page.js) في نفس العرض ويمكن التنقل بينها بشكل مستقل.

خذ على سبيل المثال مجموعتين أو أكثر من علامات التبويب داخل لوحة التحكم نفسها. يجب ألا يؤثر التنقل في مجموعة علامات تبويب واحدة على الأخرى. كما يجب استعادة مجموعات علامات التبويب بشكل صحيح عند التنقل للخلف وللأمام.

الاصطلاح

بشكل افتراضي، تقبل التخطيطات خاصية تسمى children والتي تحتوي على تخطيط متداخل أو صفحة. يمكنك إعادة تسمية الخاصية عن طريق إنشاء "فتحة" مسماة (مجلد يتضمن بادئة @) وتداخل الأجزاء داخله.

بعد هذا التغيير، ستتلقى التخطيطات خاصية تسمى customProp بدلاً من children.

analytics/layout.js
export default function Layout({ customProp }) {
  return <>{customProp}</>;
}

يمكنك إنشاء مسارات متوازية عن طريق إضافة أكثر من فتحة مسماة في نفس المستوى. في المثال أدناه، يتم تمرير كل من @views و @audience كخصائص لتخطيط التحليلات.

يمكنك استخدام الفتحات المسماة لعرض أجزاء الأوراق في نفس الوقت.

analytics/layout.js
export default function Layout({ views, audience }) {
  return (
    <>
      <div>
        <ViewsNav />
        {views}
      </div>
      <div>
        <AudienceNav />
        {audience}
      </div>
    </>
  );
}

عندما ينتقل المستخدم لأول مرة إلى /analytics، يتم عرض جزء page.js في كل مجلد (@views و @audience).

عند التنقل إلى /analytics/subscribers، يتم تحديث @audience فقط. وبالمثل، يتم تحديث @views فقط عند التنقل إلى /analytics/impressions.

سيؤدي التنقل للخلف وللأمام إلى إعادة إنشاء المجموعة الصحيحة من المسارات المتوازية.

الجمع بين اعتراض المسارات والمسارات المتوازية

يمكنك الجمع بين اعتراض المسارات والمسارات المتوازية لتحقيق سلوكيات توجيه محددة في تطبيقك.

مثال

على سبيل المثال، عند إنشاء نافذة منبثقة، غالبًا ما تحتاج إلى أن تكون على دراية ببعض التحديات الشائعة، مثل:

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

قد ترغب في أن تقوم النافذة المنبثقة بتحديث URL عند فتحها، وأن يؤدي التنقل للخلف/للأمام إلى فتح وإغلاق النافذة المنبثقة. بالإضافة إلى ذلك، عند مشاركة عنوان URL، قد ترغب في تحميل الصفحة مع فتح النافذة المنبثقة والسياق خلفها أو قد ترغب في تحميل الصفحة بالمحتوى بدون النافذة المنبثقة.

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

باستخدام الاصطلاحات، يمكننا جعل سلوك النافذة المنبثقة يطابق سلوك التوجيه افتراضيًا.

ضع في اعتبارك هيكل المجلد هذا:

مع هذا النمط:

  • يمكن الوصول إلى محتوى /photo/[id] عبر عنوان URL داخل سياقه الخاص. كما يمكن الوصول إليه داخل نافذة منبثقة من داخل مسار /[username].
  • يجب أن يؤدي التنقل للخلف وللأمام باستخدام التنقل من جانب العميل إلى إغلاق وإعادة فتح النافذة المنبثقة.
  • يجب أن يؤدي تحديث الصفحة (التنقل من جانب الخادم) إلى نقل المستخدم إلى مسار /photo/id الأصلي بدلاً من عرض النافذة المنبثقة.

في /@modal/(..)photo/[id]/page.js، يمكنك إرجاع محتوى الصفحة مغلفًا بمكون نافذة منبثقة.

/@modal/(..)photo/[id]/page.js
export default function PhotoPage() {
  const router = useRouter();
 
  return (
    <Modal
      // يجب أن تظهر النافذة المنبثقة دائمًا عند تحميل الصفحة
      isOpen={true}
      // يجب أن يؤدي إغلاق النافذة المنبثقة إلى إعادة المستخدم إلى الصفحة السابقة
      onClose={() => router.back()}
    >
      {/* محتوى الصفحة */}
    </Modal>
  );
}

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

مسارات شرطية

في بعض الأحيان، قد تحتاج إلى معلومات ديناميكية مثل البيانات أو السياق لتحديد المسار الذي يجب عرضه. يمكنك استخدام المسارات المتوازية لتحميل مسار أو آخر بشكل شرطي.

مثال

layout.js
export async function getServerSideProps({ params }) {
  const { accountType } = await fetchAccount(params.slug);
  return { props: { isUser: accountType === 'user' } };
}
 
export default function UserOrTeamLayout({ isUser, user, team }) {
  return <>{isUser ? user : team}</>;
}

في المثال أعلاه، يمكنك إرجاع مسار user أو team اعتمادًا على الرمز. هذا يسمح لك بتحميل البيانات بشكل شرطي ومطابقة المسارات الفرعية مع خيار أو آخر.

الختام

نحن متحمسون لمستقبل التخطيطات والتوجيه و React 18 في Next.js. لقد بدأ العمل على التنفيذ وسنعلن عن الميزات بمجرد توفرها.

اترك تعليقاتك وانضم إلى النقاش على GitHub Discussions.