Next.js: توجيه App Router بنظام الملفات

آخر تحديث: 2026-08-26

توجيه نظام الملفات يشبه تصنيف الكتب في المكتبة — أسماء الملفات تحدد مسار URL، وهيكل المجلدات يعمل كجدول توجيه، دون الحاجة إلى أي تكوين يدوي.

1. ما ستتعلمه



2. قصة حقيقية لمدير تقني

(1) نقطة الألم: التكوين اليدوي للتوجيه فوضوي

Bob هو المدير التقني في شركة تجارة إلكترونية، وفريقه يحتفظ بتطبيق React SPA يحتوي على 50 صفحة. في كل مرة تُضاف صفحة جديدة، يضطر المطورون إلى تكوين ثلاثة ملفات يدويًا:

"جدول التوجيه routes.js لدينا يحتوي على 300 سطر. الأسبوع الماضي، أضاف Xiao Ming صفحة تفاصيل منتج جديدة لكنه نسي تسجيل path: '/products/:id' في جدول التوجيه، مما تسبب في خطأ 404 لم يُلاحظ لمدة ساعتين كاملتين بعد نشر الصفحة."

واجه فريق Bob مشاكل إدارة التوجيه التالية:

المشكلة التأثير التكرار
نسيان تكوين التوجيه خطأ 404 بعد النشر 1-2 مرات شهريًا
معالجة المسارات المتداخلة يدويًا كود معقد، تسلسل هرمي مربك للمسارات كل صفحة جديدة
صفحات خطأ 404/500 متفرقة غير متسقة، تجربة مستخدم سيئة صفحات متعددة
صيانة منفصلة لمسارات API مجموعة مسارات للواجهة الأمامية وأخرى للخلفية كل API

(2) حلول توجيه نظام الملفات في Next.js

اسم الملف هو URL، وهيكل المجلدات هو جدول التوجيه — لم تعد هناك حاجة لتكوين توجيه react-router-dom.

TEXT 📖 للعرض فقط
src/app/
├── page.tsx                    # → /
├── about/
│   └── page.tsx                # → /about
├── products/
│   ├── page.tsx                # → /products
│   └── [id]/
│       ├── page.tsx            # → /products/1, /products/2
│       └── reviews/
│           └── page.tsx        # → /products/1/reviews
└── api/
    └── products/
        └── route.ts            # → /api/products (GET/POST)

(3) العائد

البُعد قبل (React Router) بعد (توجيه نظام الملفات)
خطوات إضافة صفحة 3 خطوات (إنشاء مكون + تكوين توجيه + استيراد) خطوة واحدة (إنشاء ملف)
صيانة تكوين التوجيه جدول توجيه بـ 300 سطر صفر أسطر، يُحدد تلقائيًا من المجلد
أخطاء 404 عند الإطلاق 1-2 مرات شهريًا 0 مرات
إدارة موحدة لأخطاء 404/500 استيراد يدوي اصطلاحات الملفات تفعّل تلقائيًا


3. مبادئ توجيه نظام الملفات

(1) اسم الملف هو URL

100%
graph TB
    subgraph "src/app/ هيكل المجلدات"
        A[page.tsx] --> B[/]
        C[about/page.tsx] --> D[/about]
        E[products/page.tsx] --> F[/products]
        G[products/promo/page.tsx] --> H[/products/promo]
        I[products/id/page.tsx] --> J[/products/:id]
        K[dashboard/settings/page.tsx] --> L[/dashboard/settings]
    end

    style A fill:#d4edda
    style C fill:#d4edda
    style E fill:#d4edda
اسم الملف URL المقابل الوصف
app/page.tsx / الصفحة الرئيسية
app/about/page.tsx /about صفحة ثابتة
app/blog/page.tsx /blog قائمة المدونة
app/blog/[id]/page.tsx /blog/1 توجيه ديناميكي
app/dashboard/settings/page.tsx /dashboard/settings تداخل متعدد المستويات

(2) اصطلاحات الملفات الستة الرئيسية

اسم الملف الغرض مطلوب؟ سلوك التصيير
page.tsx مكون الصفحة (محتوى UI) Server Component (افتراضيًا)
layout.tsx حاوية التخطيط (حالة مستمرة) ✅ تخطيط الجذر لا يُعاد تحميله أثناء التنقل
loading.tsx شاشة هيكلية للتحميل اختياري Suspense fallback
error.tsx واجهة Error Boundary اختياري يلتقط أخطاء المكونات الفرعية
not-found.tsx صفحة 404 اختياري يُفعّل بـ notFound()
route.ts نقطة نهاية API اختياري تنفيذ من جانب الخادم

▶ مثال: إنشاء صفحتك الأولى

المخرجات:

TEXT 📖 للعرض فقط
مخطط: page.tsx; /; about/page.tsx; /about; products/page.tsx; /products.
TSX
// ============================================
// إنشاء صفحة /about
// الملف:src/app/about/page.tsx
// فقط قم بإنشاء ملف، وسيتم تسجيل التوجيه تلقائيًا
// ============================================

export default function AboutPage() {
  return (
    <div className="max-w-2xl mx-auto p-8">
      <h1 className="text-3xl font-bold">حول TaskFlow</h1>
      <p className="mt-4 text-gray-600 leading-relaxed">
        TaskFlow هي منصة تعاونية لإدارة المشاريع تساعد
        الفرق على التخطيط والتتبع وتسليم المشاريع بكفاءة. مبنية
        بـ Next.js 16 و React 19، وتوفر تحديثات فورية وتعاون
        سلس وأمان على مستوى المؤسسات.
      </p>
      <div className="mt-8 grid grid-cols-3 gap-4">
        <div className="p-4 bg-blue-50 rounded-lg text-center">
          <div className="text-2xl font-bold text-blue-600">10K+</div>
          <div className="text-sm text-gray-500">مستخدم نشط</div>
        </div>
        <div className="p-4 bg-green-50 rounded-lg text-center">
          <div className="text-2xl font-bold text-green-600">50K+</div>
          <div className="text-sm text-gray-500">مشروع</div>
        </div>
        <div className="p-4 bg-purple-50 rounded-lg text-center">
          <div className="text-2xl font-bold text-purple-600">99.9%</div>
          <div className="text-sm text-gray-500">وقت التشغيل</div>
        </div>
      </div>
    </div>
  );
}

المخرجات:

TEXT 📖 للعرض فقط
المحتوى: حول TaskFlow | TaskFlow هي منصة تعاونية | 10K+ | مستخدم نشط

المخرجات:

TEXT 📖 للعرض فقط
زر http://localhost:3000/about لترى:
حول TaskFlow
TaskFlow هي منصة تعاونية لإدارة المشاريع...
10K+  مستخدم نشط | 50K+  مشروع | 99.9%  وقت التشغيل


4. التوجيه الديناميكي

(1) التوجيه الديناميكي بمعامل واحد [id]

100%
graph LR
    A[app/products] --> B[page.tsx → /products]
    A --> C[[id]]
    C --> D[page.tsx → /products/1]
    C --> E[/products/2]

    style B fill:#d4edda
    style D fill:#cce5ff
نمط الملف مثال URL قيمة params
app/blog/[slug]/page.tsx /blog/hello-world { slug: 'hello-world' }
app/products/[id]/page.tsx /products/42 { id: '42' }
app/users/[userId]/settings/page.tsx /users/123/settings { userId: '123' }

▶ مثال: صفحة تفاصيل منتج ديناميكية

المخرجات:

TEXT 📖 للعرض فقط
مخطط: app/products; page.tsx → /products; [id; page.tsx → /products/1; /products/2.
TSX
// ============================================
// صفحة تفاصيل المنتج — توجيه ديناميكي [id]
// الملف:src/app/products/[id]/page.tsx
// ============================================

// في Next.js 16، params هو Promise (غير متزامن)
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const product = await fetch(`https://fakestoreapi.com/products/${id}`);

  return (
    <div className="container mx-auto p-8">
      <div className="flex gap-8">
        <img
          src={product.image}
          alt={product.title}
          className="w-80 h-80 object-contain"
        />
        <div>
          <h1 className="text-2xl font-bold">{product.title}</h1>
          <p className="text-xl text-green-600 font-bold mt-2">
            ${product.price}
          </p>
          <p className="text-sm text-gray-500 mt-1">
            الفئة: {product.category}
          </p>
          <p className="mt-4 text-gray-700">{product.description}</p>
        </div>
      </div>
    </div>
  );
}

المخرجات:

TEXT 📖 للعرض فقط
مكون الخادم يجلب البيانات ويعرضها.

المخرجات:

TEXT 📖 للعرض فقط
زر /products/1 لترى:
Fjallraven - Foldsack No. 1 Backpack
$109.95
الفئة: men's clothing
حقيبتك المثالية للرحلات اليومية والمشي...

(2) مسارات الالتقاط الشامل [...slug]

100%
graph TB
    A[app/docs] --> B[[...slug]]
    B --> C[page.tsx]
    C --> D[/docs/getting-started]
    C --> E[/docs/guides/installation]
    C --> F[/docs/api/authentication/overview]

    style C fill:#cce5ff
نمط الملف URL params.slug
app/docs/[...slug]/page.tsx /docs 404 (المستوى 1 مطلوب)
app/docs/[...slug]/page.tsx /docs/getting-started ['getting-started']
app/docs/[...slug]/page.tsx /docs/guides/installation ['guides', 'installation']
app/docs/[...slug]/page.tsx /docs/a/b/c ['a', 'b', 'c']

▶ مثال: صفحة توثيق بالالتقاط الشامل

المخرجات:

TEXT 📖 للعرض فقط
مخطط: app/docs; [...slug; page.tsx; /docs/getting-started; /docs/guides/installation; /docs/api/authentication/overview.
TSX
// ============================================
// توجيه الالتقاط الشامل — صفحات توثيق متعددة المستويات
// الملف:src/app/docs/[...slug]/page.tsx
// ============================================

export default async function DocsPage({
  params,
}: {
  params: Promise<{ slug: string[] }>;
}) {
  const { slug } = await params;

  return (
    <div className="container mx-auto p-8">
      <nav className="text-sm text-gray-500 mb-4">
        الرئيسية / Docs / {slug.join(" / ")}
      </nav>
      <h1 className="text-3xl font-bold">Docs: {slug.join(" > ")}</h1>
      <div className="mt-8 p-6 bg-yellow-50 border border-yellow-200 rounded-lg">
        <p className="font-medium">أنت تشاهد توثيق:</p>
        <ul className="mt-2 list-disc list-inside">
          {slug.map((segment, index) => (
            <li key={index}>
              المستوى {index + 1}: <code className="bg-gray-100 px-2 py-0.5 rounded">{segment}</code>
            </li>
          ))}
        </ul>
      </div>
      <p className="mt-4 text-gray-600">
        إجمالي العمق: {slug.length} مستويات
      </p>
    </div>
  );
}

المخرجات:

TEXT 📖 للعرض فقط
DocsPage يعرض واجهته.

المخرجات:

TEXT 📖 للعرض فقط
زر /docs/getting-started/installation لترى:
الرئيسية / Docs / getting-started / installation
Docs: getting-started > installation

أنت تشاهد توثيق:
  - المستوى 1: getting-started
  - المستوى 2: installation
إجمالي العمق: 2 مستويات

(3) الالتقاط الشامل الاختياري [[...catchAll]]

100%
graph TB
    A[app/categories] --> B[[[...catchAll]]]
    B --> C[page.tsx]
    C --> D[/categories]
    C --> E[/categories/electronics]
    C --> F[/categories/electronics/phones]

    style C fill:#d4edda
    style D fill:#d4edda
نمط الملف URL params.catchAll
app/categories/[[...catchAll]]/page.tsx /categories undefined
app/categories/[[...catchAll]]/page.tsx /categories/electronics ['electronics']
app/categories/[[...catchAll]]/page.tsx /categories/electronics/phones ['electronics', 'phones']

▶ مثال: صفحة فئات بالالتقاط الشامل الاختياري

المخرجات:

TEXT 📖 للعرض فقط
مخطط: app/categories; [[...catchAll; page.tsx; /categories; /categories/electronics; /categories/electronics/phones.
TSX
// ============================================
// الالتقاط الشامل الاختياري — صفحة تصفح الفئات
// الملف:src/app/categories/[[...categories]]/page.tsx
// كل من /categories و /categories/electronics يعملان
// ============================================

export default async function CategoriesPage({
  params,
}: {
  params: Promise<{ categories?: string[] }>;
}) {
  const { categories } = await params;

  const allProducts = await fetch("https://fakestoreapi.com/products");
  const categoriesList = [...new Set(allProducts.map(p => p.category))];

  const filteredProducts = categories
    ? allProducts.filter(p => p.category === categories[0])
    : allProducts;

  return (
    <div className="container mx-auto p-8">
      <h1 className="text-3xl font-bold mb-6">
        {categories ? `الفئة: ${categories[0]}` : "جميع الفئات"}
      </h1>

      {!categories && (
        <div className="flex gap-2 mb-8 flex-wrap">
          {categoriesList.map(cat => (
            <a
              key={cat}
              href={`/categories/${encodeURIComponent(cat)}`}
              className="px-4 py-2 bg-gray-100 rounded-full hover:bg-blue-100"
            >
              {cat}
            </a>
          ))}
        </div>
      )}

      <div className="grid grid-cols-4 gap-6">
        {filteredProducts.slice(0, 8).map(p => (
          <div key={p.id} className="border rounded-lg p-4 hover:shadow-lg">
            <img src={p.image} alt={p.title} className="h-40 mx-auto" />
            <p className="mt-2 font-medium text-sm truncate">{p.title}</p>
            <p className="text-green-600 font-bold">${p.price}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

المخرجات:

TEXT 📖 للعرض فقط
جلب البيانات من جانب الخادم يعرض قائمة العناصر.

المخرجات:

TEXT 📖 للعرض فقط
زر /categories لترى:
جميع أزرار الفئات(electronics, jewelery, men's clothing, women's clothing)

زر /categories/electronics لترى:
الفئة: electronics
عرض منتجات electronics فقط(8 بطاقات منتج)


5. loading.tsx و error.tsx

(1) loading.tsx — شاشة هيكلية للتحميل

100%
graph LR
    A[تنقل المستخدم] --> B[loading.tsx<br/>عرض هيكلي]
    B --> C[page.tsx<br/>البيانات جاهزة]
    C --> D[صفحة كاملة]

    style B fill:#f8d7da
    style C fill:#d4edda

▶ مثال: شاشة هيكلية للتحميل

المخرجات:

TEXT 📖 للعرض فقط
مخطط: تنقل المستخدم; loading.tsx عرض هيكلي; page.tsx البيانات جاهزة; صفحة كاملة.
TSX
// ============================================
// شاشة هيكلية للتحميل — تُعرض أثناء جلب بيانات الصفحة
// الملف:src/app/products/loading.tsx
// ============================================

export default function ProductsLoading() {
  return (
    <div className="container mx-auto p-8">
      <div className="h-8 w-48 bg-gray-200 rounded animate-pulse mb-6" />
      <div className="grid grid-cols-4 gap-6">
        {Array.from({ length: 8 }).map((_, i) => (
          <div key={i} className="border rounded-lg p-4">
            <div className="h-40 bg-gray-200 rounded animate-pulse" />
            <div className="h-4 bg-gray-200 rounded mt-2 animate-pulse" />
            <div className="h-4 w-16 bg-gray-200 rounded mt-2 animate-pulse" />
          </div>
        ))}
      </div>
    </div>
  );
}

المخرجات:

TEXT 📖 للعرض فقط
يعرض قائمة عناصر باستخدام .map().

المخرجات:

TEXT 📖 للعرض فقط
عند زيارة /products، يُعرض حتى اكتمال تحميل البيانات:
8 بطاقات رمادية وهمية(متحركة بنبضات)
بعد انتهاء تحميل البيانات، يُستبدل المحتوى الوهمي تلقائيًا بالمحتوى الفعلي
بدون وميض، بدون شاشة بيضاء، انتقال سلس

(2) error.tsx — Error Boundary

▶ مثال: صفحة خطأ

المخرجات:

TEXT 📖 للعرض فقط
تُعرض الصفحة كما هو موصوف أعلاه، مع تحديث الواجهة بناءً على السلوك الموصوف.
TSX
// ============================================
// Error Boundary — التقاط أخطاء المكونات الفرعية
// الملف:src/app/products/error.tsx
// ============================================

'use client';

export default function ProductsError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div className="container mx-auto p-8 text-center">
      <div className="max-w-md mx-auto">
        <h2 className="text-2xl font-bold text-red-600 mb-4">
          حدث خطأ ما!
        </h2>
        <p className="text-gray-600 mb-6">
          {error.message || "فشل تحميل المنتجات. يرجى المحاولة مرة أخرى."}
        </p>
        <button
          onClick={reset}
          className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
        >
          حاول مرة أخرى
        </button>
      </div>
    </div>
  );
}

المخرجات:

TEXT 📖 للعرض فقط
المحتوى: حدث خطأ ما! | حاول مرة أخرى

المخرجات:

TEXT 📖 للعرض فقط
عند فشل جلب البيانات من صفحة المنتج، يُعرض:
حدث خطأ ما!
فشل تحميل المنتجات. يرجى المحاولة مرة أخرى.
[حاول مرة أخرى] زر → اضغط لإعادة المحاولة تلقائيًا

(3) not-found.tsx — صفحة 404



6. Route Handlers (مسارات API)

(1) الاستخدام الأساسي لـ route.ts

100%
graph LR
    A[طلب العميل] --> B[route.ts]
    B --> C[GET /api/products]
    B --> D[POST /api/products]
    B --> E[PUT /api/products/:id]
    B --> F[DELETE /api/products/:id]

    style B fill:#cce5ff
طريقة HTTP الدالة المُصدّرة الغرض
GET export async function GET() قراءة البيانات
POST export async function POST() إنشاء بيانات
PUT export async function PUT() تحديث البيانات
DELETE export async function DELETE() حذف البيانات
PATCH export async function PATCH() تحديث جزئي


7. مثال كامل: نظام تصفح منتجات التجارة الإلكترونية

TSX
// ============================================
// مثال شامل: نظام تصفح منتجات التجارة الإلكترونية
// يغطي الصفحات والتخطيط والتحميل والخطأ وتوجيه API
// ============================================

// src/app/layout.tsx — تخطيط الجذر
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className="bg-gray-50">
        <nav className="bg-white shadow-sm p-4">
          <a href="/" className="text-xl font-bold text-blue-600">
            ShopHub
          </a>
        </nav>
        <main>{children}</main>
      </body>
    </html>
  );
}

// src/app/page.tsx — الصفحة الرئيسية
export default function HomePage() {
  return (
    <div className="container mx-auto p-8 text-center">
      <h1 className="text-4xl font-bold">مرحبًا بك في ShopHub</h1>
      <p className="mt-4 text-gray-600">
        تصفح مجموعتنا من المنتجات الرائعة.
      </p>
      <a
        href="/products"
        className="inline-block mt-6 px-8 py-3 bg-blue-600 text-white rounded-lg"
      >
        تصفح المنتجات
      </a>
    </div>
  );
}

// src/app/products/page.tsx — قائمة المنتجات
export default async function ProductsPage() {
  const products = await fetch(
    "https://fakestoreapi.com/products"
  );

  return (
    <div className="container mx-auto p-8">
      <h1 className="text-3xl font-bold mb-6">جميع المنتجات</h1>
      <div className="grid grid-cols-4 gap-6">
        {products.map(p => (
          <a
            key={p.id}
            href={`/products/${p.id}`}
            className="border rounded-lg p-4 bg-white hover:shadow-lg"
          >
            <img
              src={p.image}
              alt={p.title}
              className="h-40 mx-auto"
            />
            <p className="mt-2 font-medium text-sm">{p.title}</p>
            <p className="text-green-600 font-bold mt-1">${p.price}</p>
          </a>
        ))}
      </div>
    </div>
  );
}

// src/app/products/[id]/page.tsx — تفاصيل المنتج
export default async function ProductDetailPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const product = await fetch(
    `https://fakestoreapi.com/products/${id}`
  );

  if (!product) {
    notFound();
  }

  return (
    <div className="container mx-auto p-8">
      <a
        href="/products"
        className="text-blue-600 hover:underline mb-4 inline-block"
      >
        &larr; العودة إلى المنتجات
      </a>
      <div className="flex gap-8 bg-white p-8 rounded-lg shadow">
        <img
          src={product.image}
          alt={product.title}
          className="w-96 h-96 object-contain"
        />
        <div>
          <h1 className="text-3xl font-bold">{product.title}</h1>
          <p className="text-2xl text-green-600 font-bold mt-4">
            ${product.price}
          </p>
          <p className="mt-6 text-gray-700 leading-relaxed">
            {product.description}
          </p>
          <button className="mt-6 px-8 py-3 bg-blue-600 text-white rounded-lg">
            أضف إلى السلة
          </button>
        </div>
      </div>
    </div>
  );
}

// src/app/products/loading.tsx — شاشة هيكلية للتحميل
export default function ProductsLoading() {
  return (
    <div className="container mx-auto p-8">
      <div className="h-8 w-48 bg-gray-200 rounded animate-pulse mb-6" />
      <div className="grid grid-cols-4 gap-6">
        {Array.from({ length: 8 }).map((_, i) => (
          <div key={i} className="border rounded-lg p-4 bg-white">
            <div className="h-40 bg-gray-200 rounded animate-pulse" />
            <div className="h-4 bg-gray-200 rounded mt-2 animate-pulse" />
            <div className="h-4 w-16 bg-gray-200 rounded mt-2 animate-pulse" />
          </div>
        ))}
      </div>
    </div>
  );
}

// src/app/products/error.tsx — Error Boundary
'use client';
export default function ProductsError({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div className="container mx-auto p-8 text-center">
      <h2 className="text-2xl font-bold text-red-600">
        فشل تحميل المنتجات
      </h2>
      <button
        onClick={reset}
        className="mt-4 px-6 py-2 bg-blue-600 text-white rounded-lg"
      >
        حاول مرة أخرى
      </button>
    </div>
  );
}

المخرجات المتوقعة:

TEXT 📖 للعرض فقط
الصفحة الرئيسية(/):
  مرحبًا بك في ShopHub → [تصفح المنتجات]

قائمة المنتجات(/products):
  جارٍ التحميل... → 8 بطاقات هيكلية رمادية
  اكتمل التحميل → شبكة بطاقات المنتج(تشمل الصور والعنوان والسعر)

تفاصيل المنتج(/products/1):
  صورة المنتج (يسار) + العنوان/السعر/الوصف/أضف إلى السلة (يمين)
  ← العودة إلى المنتجات رابط العودة إلى القائمة

حالة الخطأ:
  فشل تحميل المنتجات
  [حاول مرة أخرى] زر

حالة 404:
  تُعالج تلقائيًا من not-found.tsx الجذر

❓ أسئلة شائعة

س هل يمكنني استخدام .jsx بدلًا من .tsx لـ page.tsx؟
ج نعم. إذا لم يكن TypeScript مفعّلًا في مشروعك، يمكنك استخدام امتداد .jsx. ومع ذلك، يوصي هذا البرنامج التعليمي بـ TypeScript، لذا جميع أمثلة الكود تستخدم .tsx.
س لماذا params هو Promise في Next.js 16؟
ج بدءًا من Next.js 15، تم تغيير params و searchParams والمتغيرات المشابهة إلى Promises لدعم إنشاء الصفحات غير المتزامن. إذا استخدمتها مباشرة params.id، فستواجه خطأ TypeScript؛ يجب عليك await params لاسترداد قيمها.
س ما العلاقة بين loading.tsx و Suspense؟
ج loading.tsx هو Suspense fallback على مستوى الصفحة. يقوم Next.js تلقائيًا بتغليف loading.tsx داخل <Suspense>. إذا كنت بحاجة إلى حالة تحميل على مستوى المكون، يجب عليك استخدام <Suspense fallback={...}> يدويًا.
س لماذا يحتاج error.tsx إلى use client؟
ج يجب أن يكون error.tsx مكون عميل (Client Component) لأنه يحتاج إلى معالجة التفاعلات (مثل زر إعادة المحاولة). لا يمكن لمكونات الخادم (Server Components) أن تحتوي على معالجات أحداث أو hooks، لذا فإن توجيه 'use client' مطلوب.
س هل يمكن أن يتعايش route.ts و page.tsx معًا؟
ج لا. تحت نفس مقطع المسار، يكون page.tsx و route.ts متنافيين. يمكن أن يحتوي المجلد على نوع واحد فقط من معالجي المسارات — إما صفحة (page.tsx) أو API (route.ts).
س ما الفرق الدقيق بين [...slug] و [[...catchAll]]؟
ج [...slug] يتطلب مقطع مسار واحدًا على الأقل (/docs/a يتطابق؛ /docs لا يتطابق ويعيد 404). [[...catchAll]] يسمح بصفر مقاطع مسار (كل من /categories و /categories/a يتطابقان)، مما يجعله مناسبًا لسيناريوهات "تصفح الفئات الاختياري".

📖 ملخص


📝 تمارين

  1. تمرين أساسي (⭐): أنشئ صفحة باسم app/blog/[slug]/page.tsx في المشروع بحيث عند زيارة /blog/hello-world، يُخرج console القيمة slug: hello-world.

  2. تمرين متقدم (⭐⭐): أنشئ app/api/todos/route.ts لتنفيذ TODO API بسيط (GET يعيد قائمة، POST ينشئ TODO)، وتحقق منه بزيارته في المتصفح.

  3. تحدٍّ (⭐⭐⭐): أنشئ تطبيقين، app/docs/[...slug]/page.tsx و app/docs/[[...slug]]/page.tsx، لاختبار سلوك الوصول إلى /docs، واستخدم جدولًا لمقارنة الفروق بينهما.

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%