التوجيه الديناميكي (Dynamic Routes): إنشاء صفحات للمنتجات والمقالات برمجياً


التوجيه الديناميكي (Dynamic Routes): إنشاء صفحات للمنتجات والمقالات برمجياً

اليوم سنتعلم كيفية بناء صفحات ديناميكية في Next.js باستخدام App Router لإنشاء مسارات مرنة للمنتجات والمقالات. سنقوم بإنشاء بنية تمكننا من عرض تفاصيل أي منتج أو مقال بناءً على معرف فريد (ID) في عنوان URL.

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

في Next.js App Router، يتم تعريف المسارات الديناميكية بوضع اسم المجلد بين قوسين مربعين []. هذا يشير إلى أن هذا الجزء من المسار متغير.

ملاحظة تقنية: يمكن أن يكون اسم المعرف (ID) أي شيء، مثل [slug] أو [productId]. سنستخدم [id] لتبسيط المثال.

لنبدأ بإنشاء مجلد products بداخله مجلد ديناميكي [id]، ثم ملف page.tsx.

<pre><code class="language-bash">
// بنية المجلدات
// app/
// └── products/
//     └── [id]/
//         └── page.tsx
    </code></pre>

هذا المسار سيتعامل مع عناوين URL مثل /products/1، /products/a-unique-product-slug.

الخطوة 2: قراءة المعرف الديناميكي من URL

داخل ملف page.tsx، يمكننا الوصول إلى المعرف الديناميكي (ID) عن طريق params التي يتم تمريرها إلى مكون الصفحة. سنقوم بإنشاء مكون بسيط يعرض هذا المعرف.

<pre><code class="language-typescript">
// app/products/[id]/page.tsx

interface ProductPageProps {
  params: {
    id: string; // المعرف الديناميكي الذي سيتم استخلاصه من URL (مثلاً: /products/123 -> id سيكون '123')
  };
}

export default function ProductPage({ params }: ProductPageProps) {
  const { id } = params; // استخراج المعرف الديناميكي من كائن params

  return (
    <div>
      <h1>صفحة المنتج: {id}</h1>
      <p>هنا يمكنك عرض تفاصيل المنتج ذو المعرف: <strong>{id}</strong></p>
      <!-- في تطبيق حقيقي، ستقوم بجلب بيانات المنتج من قاعدة بيانات هنا -->
    </div>
  );
}
    </code></pre>

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

الخطوة 3: التعامل مع البيانات الديناميكية (اختياري: جلب البيانات)

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

ملاحظة تقنية: مكونات الخادم (Server Components) في Next.js App Router تسمح بجلب البيانات مباشرة على الخادم، مما يحسن الأداء وSEO. يمكن أن تكون وظائف جلب البيانات async.

لنعدل الكود ليقوم بمحاكاة جلب البيانات.

<pre><code class="language-typescript">
// app/products/[id]/page.tsx (تعديل)

interface ProductPageProps {
  params: {
    id: string;
  };
}

// دالة وهمية لجلب بيانات المنتج
async function getProductDetails(id: string) {
  // محاكاة تأخير في الشبكة
  await new Promise(resolve => setTimeout(resolve, 500));
  // إرجاع بيانات وهمية بناءً على المعرف
  return {
    id: id,
    name: <code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">المنتج رقم ${id}</code>,
    description: <code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">هذا هو وصف مفصل للمنتج ذو المعرف ${id}.</code>,
    price: (Math.random() * 100 + 10).toFixed(2), // سعر عشوائي
  };
}

export default async function ProductPage({ params }: ProductPageProps) {
  const { id } = params;
  const product = await getProductDetails(id); // جلب بيانات المنتج باستخدام المعرف

  if (!product) {
    // يمكن هنا عرض صفحة 404 أو رسالة خطأ
    return <div>المنتج غير موجود.</div>;
  }

  return (
    <div>
      <h1>{product.name}</h1>
      <p>المعرف: {product.id}</p>
      <p>الوصف: {product.description}</p>
      <p>السعر: ${product.price}</p>
    </div>
  );
}
    </code></pre>

الآن أصبح لدينا صفحة تعرض بيانات منتج ديناميكية بناءً على المعرف في عنوان URL.

الكود النهائي الكامل

إليكم الكود الكامل لملف page.tsx الذي يوضح كيفية إنشاء صفحة منتج ديناميكية مع جلب البيانات.

<pre><code class="language-typescript">
// app/products/[id]/page.tsx

// تعريف الواجهة لخصائص المكون، تحتوي على كائن params الذي يحمل المعرف الديناميكي
interface ProductPageProps {
  params: {
    id: string; // المعرف الديناميكي للمنتج، يتم استخلاصه من عنوان URL (مثلاً: /products/123 -> id سيكون '123')
  };
}

// دالة مساعدة وهمية لجلب تفاصيل المنتج من مصدر بيانات خارجي (مثل API)
// هذه الدالة تحاكي عملية جلب البيانات غير المتزامنة
async function getProductDetails(id: string) {
  // محاكاة تأخير زمني لتمثيل استجابة الشبكة
  await new Promise(resolve => setTimeout(resolve, 700));

  // إرجاع كائن منتج وهمي بناءً على المعرف
  return {
    id: id,
    name: <code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">المنتج الرائع رقم ${id}</code>,
    description: <code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">هذا هو الوصف التفصيلي للمنتج ذو المعرف الفريد ${id}. نأمل أن يعجبك!</code>,
    price: (Math.random() * 100 + 10).toFixed(2), // توليد سعر عشوائي
    imageUrl: <code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">https://via.placeholder.com/400x300?text=Product+${id}</code> // صورة وهمية
  };
}

// مكون الصفحة الرئيسية للمنتج. يجب أن يكون async لأنه سيقوم بجلب البيانات.
export default async function ProductPage({ params }: ProductPageProps) {
  const { id } = params; // استخراج المعرف الديناميكي من خصائص المسار

  // جلب تفاصيل المنتج باستخدام المعرف
  const product = await getProductDetails(id);

  // التحقق مما إذا كان المنتج موجودًا. في حالة عدم وجوده، يمكن عرض رسالة خطأ أو إعادة توجيه.
  if (!product) {
    return (
      <div style={{ padding: '20px', textAlign: 'center' }}>
        <h2>عذراً، المنتج غير موجود.</h2>
        <p>الرجاء التأكد من صحة المعرف.</p>
      </div>
    );
  }

  // عرض تفاصيل المنتج
  return (
    <div style={{ fontFamily: 'Arial, sans-serif', padding: '20px', maxWidth: '800px', margin: 'auto', border: '1px solid #ddd', borderRadius: '8px', boxShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>
      <h1 style={{ color: '#333', borderBottom: '2px solid #eee', paddingBottom: '10px' }}>{product.name}</h1>
      <img src={product.imageUrl} alt={product.name} style={{ maxWidth: '100%', height: 'auto', borderRadius: '4px', marginBottom: '15px' }} />
      <p><strong>المعرف:</strong> {product.id}</p>
      <p><strong>الوصف:</strong> {product.description}</p>
      <p style={{ fontSize: '1.2em', fontWeight: 'bold', color: '#007bff' }}>السعر: ${product.price}</p>
      <button style={{ backgroundColor: '#28a745', color: 'white', padding: '10px 20px', border: 'none', borderRadius: '5px', cursor: 'pointer', fontSize: '1em' }}>أضف إلى السلة</button>
    </div>
  );
}
    </code></pre>

النتيجة المتوقعة

عند زيارة عنوان URL مثل /products/123 في متصفحك، ستعرض الصفحة تفاصيل المنتج ذو المعرف 123. على سبيل المثال، ستظهر العناوين التالية:

  • المنتج الرائع رقم 123 كعنوان رئيسي.
  • المعرف: 123
  • الوصف: هذا هو الوصف التفصيلي للمنتج ذو المعرف الفريد 123. نأمل أن يعجبك!
  • السعر: $XX.XX (حيث XX.XX هو سعر عشوائي).
  • صورة وهمية للمنتج.

إذا قمت بتغيير المعرف في شريط العنوان (مثلاً إلى /products/abc)، ستُعرض تفاصيل المنتج "abc" بشكل ديناميكي، مما يوضح قوة التوجيه الديناميكي في إنشاء صفحات مرنة وقابلة للتوسع.