كن مطوّر واجهات أمامية محترفا
7 دقيقة قراءة

التوجيه المتقدّم في Next.js بعمق

دليل خبير لميزات التوجيه القويّة في موجّه تطبيق Next.js مع TypeScript — مجموعات المسارات للتنظيم وتعدّد التخطيطات الجذرية، والمقاطع الديناميكية والماسكة الشاملة مع generateStaticParams، والمسارات المتوازية بفتحاتٍ مُسمّاة، والمسارات المُعترِضة للنوافذ المنبثقة، ومعالِجات المسار (route.ts) كنقاط نهاية، والوسيط (middleware) للمصادقة وإعادة التوجيه، وtemplate مقابل layout، وملفّات البيانات الوصفية — مع أخطاء شائعة وتمارين.

غطّت الأساسيات المجلّدات-تصير-مسارات، والتخطيطات، والمقاطع الديناميكية. لكن لموجّه ملفّات التطبيق طبقةً ثانية لا يتعلّمها معظم الناس — مجموعات المسارات، والمسارات المتوازية والمُعترِضة، ومعالِجات المسار، والوسيط — وهي ما يتيح بناء أشياء تبدو مستحيلةً في غيره: نافذةٌ منبثقة مدفوعة بالعنوان، ولوحان مستقلّان في تخطيطٍ واحد، ومصادقةٌ لكل طلبٍ بلا خادم. هذه التدوينة تلك الطبقة، بعمق الخبير. تفترض الأساسيات والعرض والتخزين. كل شيءٍ بـ TypeScript، وNext.js 15+.

النموذج الذهني: في موجّه التطبيق، أسماء المجلّدات والملفّات الخاصّة هي الواجهة البرمجية. الأقواس المربّعة، والأقواس الهلالية، و@، والأسماء المحجوزة (route، middleware، template، default) كلٌّ يفتح ميزة توجيه. أنت لا تهيّئ موجّهًا — بل تُسمّي المجلّدات بطريقةٍ يفهمها الإطار. تعلّم المفردات فتتبعها السلوكيات المتقدّمة.

مجموعات المسارات: نظّم دون التأثير في العنوان

غلّف اسم مجلّدٍ بأقواسٍ هلالية — (marketing) — فيصير مجموعة مسار: وسيلةٌ لتنظيم الملفّات (ومشاركة تخطيط) دون إضافة مقطعٍ إلى العنوان. app/(marketing)/about/page.tsx يظلّ يخدم /about، لا /marketing/about.

app/
  (marketing)/
    layout.tsx        ← تخطيط لصفحات التسويق فقط
    about/page.tsx    → /about
    pricing/page.tsx  → /pricing
  (app)/
    layout.tsx        ← تخطيط مختلف للتطبيق
    dashboard/page.tsx → /dashboard

استخدامان كبيران: حصر تخطيطٍ بمجموعةٍ فرعية من المسارات (صفحات التسويق تأخذ هيكلًا، والتطبيق آخر)، ولأن كل مجموعةٍ يمكن أن تملك layout.tsx خاصًّا بها يشمل <html>/<body>، تخطيطاتٌ جذرية متعدّدة في تطبيقٍ واحد. الأقواس غير مرئيةٍ في العنوان — أداة تنظيمٍ بحتة.

الديناميكيّ والماسك الشامل والمعاملات الساكنة

أبعد من [id] واحد، تأتي المقاطع الديناميكية بثلاثة أشكال:

app/shop/[category]/page.tsx        → /shop/shoes        (مقطع واحد)
app/docs/[...slug]/page.tsx         → /docs/a/b/c        (ماسك شامل: slug = ["a","b","c"])
app/docs/[[...slug]]/page.tsx       → /docs و/docs/a     (ماسك شامل اختياريّ: يطابق الأساس أيضًا)

للمسارات الديناميكية تستطيع العرض ساكنًا وقت البناء بتصدير generateStaticParams — يعرض Next ملفّ HTML لكل معاملٍ مُرجَع، محوّلًا مسارًا ديناميكيًّا إلى صفحاتٍ ساكنة كثيرة:

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map((post) => ({ slug: post.slug })); // صفحة ساكنة لكل تدوينة
}

هكذا تُطلَق مدوّنةٌ بمسار [slug] ديناميكيّ كصفحاتٍ ساكنة سريعة. المعاملات غير المُرجَعة هنا تُعرَض عند الطلب (وتُخزَّن)، ويتحكّم فيها dynamicParams.

المسارات المتوازية: صفحاتٌ متعدّدة في تخطيطٍ واحد

تتيح المسارات المتوازية لتخطيطٍ واحدٍ عرض عدّة أشجار مساراتٍ مستقلّة معًا، كلٌّ في فتحته المُسمّاة (مجلّدٌ ببادئة @). تصل الفتحات كخصائص للتخطيط:

app/dashboard/
  layout.tsx
  @team/page.tsx       ← فتحة "team"
  @analytics/page.tsx  ← فتحة "analytics"
  page.tsx             ← فتحة children الافتراضية
// app/dashboard/layout.tsx — الفتحات خصائص، لا children
export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode;
  team: React.ReactNode;
  analytics: React.ReactNode;
}) {
  return (
    <div className="grid">
      <section>{children}</section>
      <aside>{team}</aside>
      <aside>{analytics}</aside>
    </div>
  );
}

كل فتحةٍ تنتقل وتُحمَّل وتُخطئ مستقلّةً — واحدةٌ قد تعرض مؤشّرًا بينما أخرى جاهزة. تضيف default.tsx لكل فتحةٍ لتحديد ما يُعرَض حين لا تطابق فتحةٌ العنوان الحاليّ. تُشغّل المسارات المتوازية لوحات التحكّم، والعروض المنقسمة، والواجهة الشرطية (اعرض @login أو @dashboard حسب المصادقة) دون حشوها في مكوّنٍ واحد.

المسارات المُعترِضة: نوافذ منبثقة مدفوعة بالعنوان

تتيح لك المسارات المُعترِضة اعتراض تنقّلٍ وإظهار واجهةٍ مختلفة مع إبقاء عنوان الوجهة — الحالة الكلاسيكية فتح صورةٍ في نافذةٍ منبثقة من خلاصة، حيث يصير العنوان /photo/123 لكن الخلاصة تبقى خلفها. تعلّم المُعترِض ببادئةٍ بين أقواسٍ تعني "طابِق هذا المقطع من هنا":

  • (.) — المستوى نفسه
  • (..) — مستوًى أعلى
  • (...) — من جذر التطبيق

مقرونةً بفتحة مسارٍ متوازٍ، تحصل على: النقر على رابطٍ يفتح نافذةً منبثقة (مُعترَضة)، لكن زيارة /photo/123 مباشرةً (أو التحديث) تعرض الصفحة الكاملة. عنوانٌ واحد، عرضان حسب كيفية وصولك — يستحيل فعله بنظافةٍ دون مساعدة الموجّه.

app/
  feed/page.tsx
  @modal/(.)photo/[id]/page.tsx   ← يعترض /photo/[id] كنافذةٍ فوق الخلاصة
  photo/[id]/page.tsx             ← الصفحة الكاملة (زيارة مباشرة / تحديث)

معالِجات المسار: نقاط نهايةٍ في موجّه التطبيق

ليست كل نقطة نهاية خادمٍ تعديل نموذج. معالِجات المسار ملفّات route.ts تستجيب لأفعال HTTP بواجهات الويب Request/Response — نسخة موجّه التطبيق من مسارات API. مجلّدٌ فيه route.ts (بدل page.tsx) يصير نقطة نهاية:

// app/api/products/route.ts  →  GET/POST /api/products
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const products = await db.product.findMany();
  return NextResponse.json(products);
}

export async function POST(request: Request) {
  const body = await request.json();
  const created = await db.product.create({ data: body });
  return NextResponse.json(created, { status: 201 });
}

صدّر دالّةً لكل فعل (GET، POST، PUT، DELETE، …). استخدم معالِجات المسار لما لا تناسبه أفعال الخادم: الخطّافات (webhooks)، وواجهات JSON العامّة التي يستهلكها عملاءُ غير React، والاستجابات المتدفّقة، وردود OAuth. ولتعديلات النماذج العادية، فضّل فعل خادم — كودٌ أقلّ ومُنمَّطٌ من طرفٍ لطرف.

الوسيط: كودٌ قبل استقرار الطلب

يعمل الوسيط (middleware) على الحافة قبل أن يصل الطلب إلى مسار — مثاليّ لبوّابات المصادقة، وإعادة التوجيه، وإعادة الكتابة، وضبط الترويسات. ملفّ middleware.ts واحدٌ في جذر المشروع، بـ matcher لحصر المسارات التي يعمل عليها:

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const token = request.cookies.get("session")?.value;

  // احمِ لوحة التحكّم: أعِد توجيه غير المصادَقين إلى الدخول
  if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
  return NextResponse.next(); // تابِع
}

export const config = {
  matcher: ["/dashboard/:path*"], // اعمل على هذه المسارات فقط
};

الوسيط سريعٌ لكن محدود: يعمل على بيئة Edge، ولا يستطيع استخدام واجهات Node البحتة أو استعلام قاعدة بياناتٍ مباشرة، وينبغي أن يبقى خفيفًا (إنه على المسار الساخن لكل طلبٍ مطابق). استخدمه للبوّابات الخشنة وإعادة التوجيه؛ وأجرِ التفويض الحقيقيّ داخل الصفحة أو الفعل، قرب البيانات.

template.tsx مقابل layout.tsx

كلاهما يغلّف المسارات الأبناء، بفارقٍ واحد: يبقى layout مركّبًا عبر التنقّلات (تصمد الحالة والتمرير)، بينما ينشئ template نسخةً جديدة في كل تنقّل — يُعاد تركيبه، فيعيد تشغيل الآثار وإعادة ضبط الحالة. استخدم layout افتراضيًّا؛ والجأ إلى template حين تريد تحديدًا سلوكًا لكل تنقّل (حركة دخول، إعادة ضبط نموذج، إعادة إطلاق أثرٍ عند كل تغيير مسار).

ملفّات البيانات الوصفية

بعض التوجيه يعيش في ملفّاتٍ خاصّة تولّد أصول SEO وPWA من الكود. ضع هذه في app/ فيخدمها Next في العنوان الصحيح:

// app/sitemap.ts  →  /sitemap.xml
import type { MetadataRoute } from "next";
export default function sitemap(): MetadataRoute.Sitemap {
  return [{ url: "https://example.com", lastModified: new Date() }];
}

// app/robots.ts  →  /robots.txt   ·   app/opengraph-image.tsx → صورة OG ديناميكية

اصطلاحات الملفّات هذه (sitemap، robots، manifest، opengraph-image، icon) تعني أن أصول SEO/التواصل تُولَّد وتُنمَّط بجوار مساراتك بدل صيانتها يدويًّا.

الأخطاء الشائعة

  • توقّع ظهور (group) في العنوان — مجموعات المسارات تنظّم الملفّات وتحصر التخطيطات؛ والأقواس تُنزَع من المسار.
  • نسيان default.tsx للفتحات المتوازية — بدونه، قد تُعطِب فتحةٌ لا تطابق العنوان الحاليّ المسار كله (404) عند التنقّل/التحديث.
  • الخلط بين الماسك الشامل والاختياريّ[...slug] لا يطابق المسار الأساس؛ و[[...slug]] يطابقه. اختر بناءً على وجوب مطابقة المسار المجرّد.
  • إجراء عملٍ ثقيل في الوسيط — إنه على كل طلبٍ مطابق ويعمل على Edge. أبقِه على إعادة التوجيه/الكتابة/الترويسات؛ بلا استدعاءات قاعدة بياناتٍ أو منطقٍ بطيء.
  • اعتبار مصادقة الوسيط كافية — إنها بوّابةٌ خشنة؛ فوِّض داخل الصفحة/الفعل حيث تمسّ البيانات.
  • بناء معالِج مسارٍ لتعديل نموذج — فعل الخادم عادةً كودٌ أقلّ وأأمن. احفظ معالِجات المسار للخطّافات، والواجهات العامّة، والعملاء غير النماذج.
  • اللجوء إلى template افتراضيًّا — يُعاد تركيبه في كل تنقّل (فتُفقَد الحالة)؛ استخدم layout إلا إن احتجت الإعادة تحديدًا.

تمارين

جرّب كلًّا قبل فتح الحلّ.

تمرين 1 — جمّع دون مقطع عنوان

تريد أن يتشارك /login و/register تخطيط "auth"، لكن يجب أن تبقى العناوين /login و/register. كيف؟

اعرض الحل

ضعهما في مجموعة مسار:

app/(auth)/layout.tsx
app/(auth)/login/page.tsx     → /login
app/(auth)/register/page.tsx  → /register

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

تمرين 2 — ولّد مسارًا ديناميكيًّا ساكنًا

اجعل app/products/[id]/page.tsx يعرض صفحةً ساكنة لكل منتجٍ وقت البناء.

اعرض الحل
export async function generateStaticParams() {
  const products = await getAllProducts();
  return products.map((p) => ({ id: p.id }));
}

يعرض Next صفحةً ساكنة لكل id مُرجَع، فيُطلَق المسار الديناميكيّ كملفّاتٍ ساكنة سريعة كثيرة.

تمرين 3 — معالِج مسارٍ أم فعل خادم؟

خطّاف Stripe يحتاج إرسال POST إلى تطبيقك. معالِج مسارٍ أم فعل خادم، ولماذا؟

اعرض الحل

معالِج مسار (app/api/webhooks/stripe/route.ts بتصدير POST). أفعال الخادم لنماذجك/مكوّناتك أنت؛ أما الخطّاف فخدمةٌ خارجية تستدعي نقطة نهاية HTTP قياسية، وهو بالضبط ما يكشفه معالِج المسار (مع الوصول إلى Request الخام، والترويسات، والتحقّق من التوقيع).

النموذج الذهني الذي تحتفظ به

في موجّه التطبيق، الأسماء الخاصّة هي واجهة التوجيه: مجلّدات (group) تنظّم الملفّات وتحصر التخطيطات دون مسّ العنوان (وتتيح تخطيطاتٍ جذرية متعدّدة)؛ و[id]/[...slug]/[[...slug]] مقاطع ديناميكية وماسكة شاملة واختيارية، تُجعَل ساكنةً بـ generateStaticParams؛ ومجلّدات @slot مسارات متوازية تعرض أشجارًا مستقلّة في تخطيطٍ واحد؛ وبوادئ (.)/(..)/(...) مسارات مُعترِضة لنوافذ منبثقة مدفوعة بالعنوان. ملفّات route.ts معالِجات مسار — نقاط نهاية HTTP للخطّافات والواجهات العامّة (استخدم أفعال الخادم لتعديلات نماذجك)، و**middleware.ts** جذريٌّ يعمل قبل الطلبات المطابقة لمصادقةٍ خشنة وإعادة توجيهٍ وكتابة (أبقِه خفيفًا؛ وفوِّض حقًّا قرب البيانات). أكمِلها بـ template مقابل layout (إعادة تركيبٍ مقابل بقاء) وملفّات البيانات الوصفية (sitemap، robots، opengraph-image) لـ SEO. تعلّم مفردات المجلّدات فتصير أقوى سلوكيات توجيه موجّه التطبيق مجرّد أسماءٍ تكتبها.