Next.js: التنقل: Link وواجهة برمجة التطبيقات البرمجية

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

التنقل يشبه نظام مترو الأنفاق - هناك خطوط آلية (مكوّنات Link)، وخيارات للتحكم اليدوي (التنقل البرمجي)، وقطارات سريعة للتنقلات الخاطفة (soft navigation)، ومسارات تحويلية (hard navigation).

1. ما ستتعلمه



2. قصة حقيقية لمطور مبتدئ

(1) نقطة الألم: تجربة تنقل سيئة بين الصفحات

تشارلي كان يعمل مؤخرًا على تطوير موقع تجارة إلكترونية باستخدام Next.js واكتشف أن تجربة التنقل سيئة:

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

السؤال المحدد:

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

(2) الحلول للتنقل في Next.js

مكوّن Link مع الجلب التلقائي + Soft Navigation مع الحفاظ على الحالة + scroll={false} للحفاظ على موضع التمرير.

TSX
// تنقل محسّن في صفحة قائمة المنتجات
<Link
  href={`/products/${product.id}`}
  prefetch={true}
  scroll={false}
  className="block p-4 border rounded hover:shadow"
>
  {product.title}
</Link>

(3) النتائج

البُعد قبل التحسين (وسم <a> العادي) بعد التحسين (مكوّن <Link>)
سرعة تحميل الصفحة 1–2 ثانية (شاشة فارغة) فورية (ذاكرة تخزين مؤقت للجلب المسبق)
استمرارية بيانات النموذج تُفقد عند إلغاء تحميل الصفحة محفوظة في Soft Nav
الاحتفاظ بموضع التمرير العودة إلى الأعلى احتفاظ دقيق
تقييم تجربة المستخدم 3.2/5 4.8/5


(1) الاستخدام الأساسي

100%
graph LR
    A[مكوّنات Link] --> B[تنقل من جانب العميل<br/>بدون تحديث كامل للصفحة]
    A --> C[جلب مسبق تلقائي<br/>للروابط داخل نطاق العرض]
    A --> D[التحكم في التمرير<br/>scroll={false}]
    A --> E[استبدال السجل<br/>replace]

    style A fill:#cce5ff
    style B fill:#d4edda

المخرجات:

TEXT 📖 للعرض فقط
رسم توضيحي للتنقل من جانب العميل باستخدام مكوّن Link.
TSX
// ============================================
// الاستخدام الأساسي لمكوّنات Link
// ============================================

import Link from "next/link";

export default function Navigation() {
  return (
    <nav className="flex gap-6 p-4 bg-white shadow-sm">
      {/* تنقل أساسي */}
      <Link href="/" className="text-blue-600 hover:underline">
        الرئيسية
      </Link>

      {/* توجيه ديناميكي */}
      <Link href="/products/42" className="text-blue-600 hover:underline">
        المنتج 42
      </Link>

      {/* مع معاملات الاستعلام */}
      <Link
        href="/products?category=electronics&sort=price"
        className="text-blue-600 hover:underline"
      >
        إلكترونيات
      </Link>

      {/* رابط كامل */}
      <Link
        href="https://help.example.com"
        className="text-blue-600 hover:underline"
      >
        مركز المساعدة
      </Link>
    </nav>
  );
}

المخرجات:

TEXT 📖 للعرض فقط
يعرض: روابط تنقل باستخدام مكوّن Link في Next.js للتوجيه من جانب العميل.

المخرجات:

TEXT 📖 للعرض فقط
يعرض المتصفح 4 روابط زرقاء:
الرئيسية → / 
المنتج 42 → /products/42
إلكترونيات → /products?category=electronics&sort=price
مركز المساعدة → https://help.example.com(رابط خارجي)

(2) prefetch

بشكل افتراضي، يقوم مكوّن Link بالتحميل المسبق للروابط الموجودة داخل نطاق العرض. يختلف سلوك التحميل المسبق بين الخادم والعميل:

البيئة prefetch={true} prefetch={false}
التصيير من جانب الخادم تحميل مسبق لبيانات الصفحة وحمولة RSC لا تحميل مسبق
الصفحة الثابتة تحميل مسبق للصفحة بالكامل لا تحميل مسبق
داخل نطاق العرض السلوك الافتراضي لا تحميل مسبق أبدًا

▶ مثال: التحكم في سلوك prefetch

المخرجات:

TEXT 📖 للعرض فقط
يتم تصيير الصفحة كما هو موصوف أعلاه، مع تحديث واجهة المستخدم بناءً على السلوك الموصوف.
TSX
// ============================================
// التحكم في سلوك التحميل المسبق لـ Link
// ============================================

import Link from "next/link";

export default function ProductList() {
  const products = [
    { id: 1, name: "فأرة لاسلكية" },
    { id: 2, name: "لوحة مفاتيح" },
    { id: 3, name: "شاشة" },
  ];

  return (
    <div className="p-8">
      <h1 className="text-2xl font-bold mb-4">المنتجات</h1>

      {/* prefetch الافتراضي: تحميل مسبق تلقائي للروابط داخل نطاق العرض */}
      {products.map(p => (
        <Link
          key={p.id}
          href={`/products/${p.id}`}
          className="block p-4 border-b hover:bg-gray-50"
        >
          {p.name}
        </Link>
      ))}

      {/* إيقاف التحميل المسبق: مناسب للصفحات التي لا تُستخدم كثيرًا */}
      <Link
        href="/archive"
        prefetch={false}
        className="block mt-4 text-gray-500"
      >
        عرض الأرشيف (منتجات أقدم)
      </Link>

      {/* تحميل مسبق إجباري: مناسب للصفحة التالية التي أنت على وشك زيارتها */}
      <Link
        href="/checkout"
        prefetch={true}
        className="block mt-4 px-6 py-2 bg-blue-600 text-white text-center rounded"
      >
        متابعة إلى الدفع
      </Link>
    </div>
  );
}

المخرجات:

TEXT 📖 للعرض فقط
يعرض قائمة المنتجات عبر التكرار.
المحتوى: المنتجات

المخرجات:

TEXT 📖 للعرض فقط
بعد تحميل الصفحة، يمكن ملاحظة ذلك في وحدة تحكم الشبكة:
1. /products/1، /products/2، /products/3 تم جلبها مسبقًا (داخل نطاق العرض)
2. /archive لم يتم جلبها مسبقًا(prefetch={false})
3. /checkout تم جلبها مسبقًا(prefetch={true}، حتى لو لم تكن ضمن نطاق العرض)

عندما ينقر المستخدم على الرابط:
- الصفحات التي تم جلبها مسبقًا → عرض فوري(قراءة من ذاكرة التخزين المؤقت)
- الصفحات التي لم يتم جلبها مسبقًا → تُعرض بعد طلب الشبكة

(3) التحكم في التمرير

100%
graph LR
    A[تشغيل التنقل] --> B{خاصية scroll}
    B -->|scroll=true افتراضي| C[التمرير إلى أعلى الصفحة الجديدة]
    B -->|scroll=false| D[الحفاظ على موضع التمرير الحالي]

    style C fill:#f8d7da
    style D fill:#d4edda

▶ مثال: scroll={false} للحفاظ على موضع التمرير

المخرجات:

TEXT 📖 للعرض فقط
رسم توضيحي: تشغيل التنقل؛ التمرير إلى أعلى الصفحة الجديدة؛ الحفاظ على موضع التمرير الحالي.
TSX
// ============================================
// قائمة المنتجات + نافذة منبثقة: scroll=false للحفاظ على القائمة في مكانها
// ينقر المستخدم على منتج لعرض تفاصيله، لا يتم التمرير عند العودة
// ============================================

import Link from "next/link";

export default function ProductGrid() {
  const products = Array.from({ length: 20 }, (_, i) => ({
    id: i + 1,
    name: `المنتج ${i + 1}`,
  }));

  return (
    <div className="p-8">
      <h1 className="text-2xl font-bold mb-6">كتالوج المنتجات</h1>
      <div className="grid grid-cols-4 gap-4">
        {products.map(p => (
          <Link
            key={p.id}
            href={`/products/${p.id}`}
            scroll={false}
            className="border p-4 rounded hover:shadow-lg"
          >
            <div className="h-32 bg-gray-100 rounded" />
            <p className="mt-2 font-medium">{p.name}</p>
          </Link>
        ))}
      </div>
    </div>
  );
}

المخرجات:

TEXT 📖 للعرض فقط
يعرض قائمة المنتجات عبر التكرار.
المحتوى: كتالوج المنتجات

المخرجات:

TEXT 📖 للعرض فقط
1. قام المستخدم بالتمرير إلى الصف الثالث(تم التمرير للأسفل 2000px)
2. النقر على المنتج 15 للانتقال إلى صفحة تفاصيل المنتج
3. النقر على زر الرجوع في المتصفح
4. العودة إلى صفحة القائمة، مع الحفاظ على موضع التمرير عند 2000px(لا يوجد "عودة إلى الأعلى")
5. للمقارنة: إذا لم يتم استخدام scroll={false}، سيتم العودة إلى الأعلى في كل مرة

(4) replace: استبدال السجل

الإجراء push (افتراضي) replace
سجل المتصفح إضافة إدخال جديد استبدال الإدخال الحالي
سلوك زر الرجوع العودة إلى الصفحة السابقة الانتقال إلى الصفحة التي تم استبدالها
بعد إرسال النموذج لا يمكن العودة إلى صفحة النموذج يمكن العودة (تجاوز صفحة النموذج)

▶ مثال: استخدام replace في سيناريو النموذج

المخرجات:

TEXT 📖 للعرض فقط
يقوم المكوّن بتصيير واجهة المستخدم الموصوفة في المتصفح.
TSX
// ============================================
// replace استبدال السجل: لا يمكن العودة إلى صفحة النموذج بعد الإرسال
// ============================================

import Link from "next/link";

export default function CheckoutPage() {
  return (
    <div className="max-w-md mx-auto p-8">
      <h1 className="text-2xl font-bold mb-6">الدفع</h1>

      <div className="space-y-4">
        <input placeholder="رقم البطاقة" className="w-full p-3 border rounded" />
        <input placeholder="تاريخ الانتهاء" className="w-full p-3 border rounded" />
        <input placeholder="CVV" className="w-full p-3 border rounded" />
      </div>

      {/* استخدام replace: بمجرد الإرسال، لا يمكن العودة إلى هذه الصفحة */}
      <Link
        href="/order-confirmation"
        replace
        className="block mt-6 w-full p-3 bg-blue-600 text-white text-center rounded"
      >
        تقديم الطلب
      </Link>

      <p className="text-sm text-gray-500 mt-2 text-center">
        بعد تقديم الطلب، سيتجاوز زر الرجوع هذه الصفحة
      </p>
    </div>
  );
}

المخرجات:

TEXT 📖 للعرض فقط
المحتوى: الدفع | تقديم الطلب | بعد تقديم الطلب، سيتجاوز زر الرجوع

المخرجات:

TEXT 📖 للعرض فقط
تدفق المستخدم:
1. الرئيسية → سلة التسوق → الدفع → صفحة التأكيد
2. النقر على "تقديم الطلب" في صفحة الدفع
3. يتغير عنوان المتصفح إلى /order-confirmation
4. ينقر المستخدم على زر الرجوع
5. يتجاوز صفحة الدفع، وينتقل مباشرة إلى صفحة سلة التسوق
6. يمنع المستخدمين من العودة بطريق الخطأ إلى صفحة الدفع وإعادة تقديم الطلب


4. التنقل البرمجي باستخدام useRouter

(1) مرجع سريع لواجهة برمجة التطبيقات

100%
graph TB
    A[useRouter] --> B[push(url) - الانتقال إلى صفحة جديدة]
    A --> C[replace(url) - استبدال السجل الحالي]
    A --> D[back() - الرجوع]
    A --> E[forward() - التقدم]
    A --> F[refresh() - تحديث هذه الصفحة]
    A --> G[prefetch(url) - جلب مسبق برمجي]

    style A fill:#cce5ff
الدالة المعاملات الوصف سلوك المتصفح
push href: string الانتقال إلى عنوان URL الجديد إضافة إلى السجل
replace href: string استبدال السجل الحالي استبدال السجل
back لا شيء رجوع المتصفح نفس history.back()
forward لا شيء تقدم المتصفح نفس history.forward()
refresh لا شيء تحديث هذه الصفحة تصيير من جانب الخادم (RSC)
prefetch href: string جلب مسبق للصفحة تخزين حمولة RSC مؤقتًا

▶ مثال: التنقل البرمجي

المخرجات:

TEXT 📖 للعرض فقط
رسم توضيحي للتنقل من جانب العميل باستخدام مكوّن Link.
TSX
// ============================================
// useRouter مثال كامل للتنقل البرمجي
// ============================================

'use client';

import { useRouter } from "next/navigation";

export default function NavigationBar({ userId }: { userId: string }) {
  const router = useRouter();

  return (
    <div className="p-4 bg-white shadow-sm">
      <div className="flex gap-4 max-w-4xl mx-auto">
        {/* push: الانتقال إلى الصفحة الرئيسية */}
        <button
          onClick={() => router.push("/")}
          className="px-4 py-2 bg-blue-600 text-white rounded"
        >
          الرئيسية
        </button>

        {/* push: توجيه ديناميكي */}
        <button
          onClick={() => router.push(`/users/${userId}`)}
          className="px-4 py-2 bg-gray-600 text-white rounded"
        >
          الملف الشخصي
        </button>

        {/* replace: استبدال الصفحة الحالية */}
        <button
          onClick={() => router.replace("/login")}
          className="px-4 py-2 bg-red-600 text-white rounded"
        >
          تسجيل الخروج
        </button>

        {/* back/forward: سجل التصفح */}
        <button
          onClick={() => router.back()}
          className="px-4 py-2 border rounded"
        >
          ← رجوع
        </button>
        <button
          onClick={() => router.forward()}
          className="px-4 py-2 border rounded"
        >
          تقدم →
        </button>

        {/* refresh: تحديث هذه الصفحة(إعادة تصيير من جانب الخادم) */}
        <button
          onClick={() => router.refresh()}
          className="px-4 py-2 border rounded"
        >
          تحديث ↻
        </button>
      </div>
    </div>
  );
}

المخرجات:

TEXT 📖 للعرض فقط
يقوم NavigationBar بتصيير واجهة المستخدم الخاصة به.

المخرجات:

TEXT 📖 للعرض فقط
النقر على كل زر:
[الرئيسية]    → الانتقال إلى /
[الملف الشخصي] → الانتقال إلى /users/123
[تسجيل الخروج]  → استبدال السجل الحالي بـ /login(لا يمكن الرجوع)
[← رجوع]  → زر الرجوع في المتصفح
[تقدم] → زر التقدم في المتصفح
[تحديث] → تحديث هذه الصفحة(إعادة تصيير RSC، لا يتسبب في إعادة تحميل الصفحة بالكامل)

▶ مثال: التنقل بعد إرسال النموذج

المخرجات:

TEXT 📖 للعرض فقط
يتم تصيير الصفحة كما هو موصوف أعلاه، مع تحديث واجهة المستخدم بناءً على السلوك الموصوف.
TSX
// ============================================
// إرسال النموذج: التحقق → الحفظ → التنقل البرمجي
// ============================================

'use client';

import { useRouter } from "next/navigation";
import { useState } from "react";

export default function CreateProjectForm() {
  const router = useRouter();
  const [saving, setSaving] = useState(false);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setSaving(true);

    try {
      // محاكاة استدعاء API
      await new Promise(resolve => setTimeout(resolve, 1500));
      // بعد الانتهاء بنجاح، الانتقال إلى صفحة المشروع الجديد
      router.push("/projects/42");
      // وتحديث ذاكرة التخزين المؤقت للبيانات
      router.refresh();
    } catch (err) {
      console.error("فشل إنشاء المشروع:", err);
      alert("فشل إنشاء المشروع. يرجى المحاولة مرة أخرى.");
    } finally {
      setSaving(false);
    }
  }

  return (
    <form onSubmit={handleSubmit} className="max-w-lg mx-auto p-8 space-y-4">
      <h1 className="text-2xl font-bold">إنشاء مشروع</h1>
      <input
        name="name"
        placeholder="اسم المشروع"
        className="w-full p-3 border rounded"
        required
      />
      <textarea
        name="description"
        placeholder="الوصف"
        className="w-full p-3 border rounded h-32"
      />
      <button
        type="submit"
        disabled={saving}
        className="w-full p-3 bg-blue-600 text-white rounded disabled:opacity-50"
      >
        {saving ? "جارٍ الإنشاء..." : "إنشاء مشروع"}
      </button>
    </form>
  );
}

المخرجات:

TEXT 📖 للعرض فقط
نموذج يحتوي على: name.
المحتوى: إنشاء مشروع

المخرجات:

TEXT 📖 للعرض فقط
1. يقوم المستخدم بإدخال اسم المشروع والوصف
2. النقر على زر "إنشاء مشروع"
3. يتغير الزر إلى "جارٍ الإنشاء..."(معطل)
4. بعد 1.5 ثانية (محاكاة تأخير API)
5. انتقال الصفحة إلى /projects/42
6. تحديث بيانات قائمة المشاريع(تشمل المشروع المنشأ حديثًا)


5. redirect() و <Redirect>

(1) نوعان من إعادة التوجيه

100%
graph TB
    A[طلبات إعادة التوجيه] --> B[جانب الخادم<br/>redirect()]
    A --> C[جانب العميل<br/><Redirect>]
    B --> D[Server Action / Route Handler]
    B --> E[Server Component]
    C --> F[Client Component]
    C --> G[عند استخدام التصيير الشرطي]

    style B fill:#d4edda
    style C fill:#cce5ff
الطريقة مكان الاستخدام متى يتم التشغيل الأداء
redirect() Server Actions / Server Components أثناء استجابة الخادم إعادة توجيه من الخادم، بدون حمل على العميل
<Redirect> Client Components أثناء التصيير إعادة توجيه من جانب العميل، تغيير المسار


6. Soft Navigation مقابل Hard Navigation

(1) المقارنة

100%
graph TB
    subgraph "Soft Navigation(Link/useRouter)"
        A[توجيه من جانب العميل] --> B[تنقل بين الصفحات<br/>بدون تحديث كامل للصفحة]
        B --> C[layout يبقى مثبّتًا]
        B --> D[الحفاظ على حالة React]
        B --> E[استبدال حمولة RSC]
    end

    subgraph "Hard Navigation(تحديث صفحة المتصفح بالكامل)"
        F[تحميل كامل للصفحة في المتصفح] --> G[تنقل بين الصفحات<br/>تحديث الصفحة بالكامل]
        G --> H[إعادة تثبيت layout]
        G --> I[إعادة تعيين جميع الحالات]
        G --> J[تحميل كامل لـ JS/CSS]
    end

    style A fill:#d4edda
    style F fill:#f8d7da
الخاصية Soft Navigation Hard Navigation
طريقة التشغيل <Link>، useRouter() تحديث المتصفح، وسم <a>، window.location
تحديث الصفحة بالكامل ❌ لا ✅ نعم
الحفاظ على Layout ✅ محفوظ ❌ إعادة تثبيت
حمولة RSC تحديث تزايدي تحميل كامل
الأداء فوري 500 مللي ثانية–2 ثانية
الحفاظ على الحالة


7. مؤشر تحميل التنقل

(1) خطة التنفيذ

100%
graph TB
    A[تشغيل التنقل] --> B[مؤشر التحميل]
    B --> C[شريط تقدم علوي<br/>بأسلوب NProgress]
    B --> D[Suspense fallback<br/>عرض هيكلي]
    B --> E[loading.tsx<br/>تحميل على مستوى الصفحة]

    style A fill:#cce5ff
    style B fill:#f8d7da


8. مثال كامل: نظام تنقل للتجارة الإلكترونية

TSX
// ============================================
// مثال شامل: نظام تنقل متكامل للتجارة الإلكترونية
// يغطي Link وuseRouter وredirect وحالة تحميل التنقل
// ============================================

// src/app/layout.tsx — شريط تنقل عام
import Link from "next/link";
import { CartCount } from "./CartCount";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <header className="bg-white shadow-sm sticky top-0 z-40">
          <div className="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between">
            <Link href="/" className="text-2xl font-bold text-blue-600">
              ShopHub
            </Link>

            <nav className="hidden md:flex gap-6">
              <Link href="/products" className="hover:text-blue-600">
                المنتجات
              </Link>
              <Link href="/categories" className="hover:text-blue-600">
                الفئات
              </Link>
              <Link href="/deals" prefetch={false} className="hover:text-blue-600">
                العروض
              </Link>
              <Link href="/about" className="hover:text-blue-600">
                حول
              </Link>
            </nav>

            <div className="flex items-center gap-4">
              <Link href="/search" className="text-gray-600 hover:text-blue-600">
                بحث
              </Link>
              <Link href="/cart" className="relative text-gray-600 hover:text-blue-600">
                السلة
                <CartCount />
              </Link>
              <Link
                href="/account"
                className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm"
              >
                تسجيل الدخول
              </Link>
            </div>
          </div>
        </header>

        <main className="max-w-6xl mx-auto px-4 py-8">
          {children}
        </main>
      </body>
    </html>
  );
}

// src/app/products/[id]/page.tsx — تفاصيل المنتج(تشمل منطق التنقل)
'use client';
import { useRouter } from "next/navigation";
import Link from "next/link";

export default function ProductDetail({ params }) {
  const router = useRouter();

  async function handleAddToCart() {
    await fetch("/api/cart", {
      method: "POST",
      body: JSON.stringify({ productId: params.id }),
    });
    // تحديث العدد في سلة التسوق بعد إضافة عنصر
    router.refresh();
  }

  async function handleBuyNow() {
    await fetch("/api/cart", {
      method: "POST",
      body: JSON.stringify({ productId: params.id, quantity: 1 }),
    });
    // شراء الآن → متابعة إلى الدفع (استخدام replace لتجنب العودة إلى صفحة المنتج)
    router.replace("/checkout");
  }

  return (
    <div>
      {/* تنقل تسلسلي */}
      <nav className="text-sm text-gray-500 mb-6">
        <Link href="/" className="hover:text-blue-600">الرئيسية</Link>
        <span className="mx-2">/</span>
        <Link href="/products" className="hover:text-blue-600">المنتجات</Link>
        <span className="mx-2">/</span>
        <span className="text-gray-900">{params.id}</span>
      </nav>

      <div className="flex gap-8">
        <div className="w-1/2">
          <img
            src={`https://picsum.photos/seed/${params.id}/400/400`}
            alt="المنتج"
            className="w-full rounded-lg"
          />
        </div>
        <div className="w-1/2">
          <h1 className="text-3xl font-bold">المنتج #{params.id}</h1>
          <p className="text-2xl text-green-600 font-bold mt-4">$49.99</p>
          <p className="text-gray-600 mt-4">
            منتج عالي الجودة بميزات متميزة.
          </p>

          <div className="flex gap-4 mt-8">
            <button
              onClick={handleAddToCart}
              className="flex-1 px-6 py-3 border-2 border-blue-600 text-blue-600 rounded-lg hover:bg-blue-50"
            >
              أضف إلى السلة
            </button>
            <button
              onClick={handleBuyNow}
              className="flex-1 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
            >
              شراء الآن
            </button>
          </div>

          <div className="mt-8 border-t pt-6">
            <Link
              href={`/products/${Number(params.id) - 1}`}
              scroll={false}
              className="text-blue-600 hover:underline"
            >
              ← المنتج السابق
            </Link>
            <Link
              href={`/products/${Number(params.id) + 1}`}
              scroll={false}
              className="text-blue-600 hover:underline float-right"
            >
              المنتج التالي →
            </Link>
          </div>
        </div>
      </div>
    </div>
  );
}

// src/app/CartCount.tsx — عرض عدد عناصر سلة التسوق
export async function CartCount() {
  const cart = await fetch("https://api.example.com/cart");
  const itemCount = cart?.items?.length ?? 0;

  if (itemCount === 0) return null;

  return (
    <span className="absolute -top-2 -right-2 bg-red-500 text-white text-xs w-5 h-5 rounded-full flex items-center justify-center">
      {itemCount}
    </span>
  );
}

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

TEXT 📖 للعرض فقط
شريط التنقل(مثبت في الأعلى):
[ShopHub]  المنتجات  الفئات  العروض  حول  [بحث]  السلة(3)  [تسجيل الدخول]

صفحة تفاصيل المنتج(/products/1):
الرئيسية / المنتجات / 1
[صورة المنتج]  [تفاصيل المنتج]
            المنتج #1
            $49.99
            [أضف إلى السلة] [شراء الآن]
            ← المنتج السابق | المنتج التالي →

سلوك التنقل:
- النقر على المنتجات → Soft Nav، تبديل فوري للصفحات
- النقر على العروض → لا يوجد جلب مسبق(prefetch=false)، لكن التنقل لا يزال سريعًا
- إضافة إلى السلة → router.refresh()، تحديث رمز سلة التسوق
- شراء الآن → router.replace("/checkout")، استبدال السجل

❓ أسئلة شائعة

س ما الفرق بين مكوّن Link ووسم <a>؟
ج مكوّن Link يتولى التوجيه من جانب العميل دون تشغيل تحديث كامل للصفحة، ويحافظ على layout، ويدعم التحميل المسبق. وسم <a> يشغّل إعادة تحميل كاملة للصفحة (hard navigation)، مما يتسبب في فقدان جميع حالات React. يستخدم Next.js مكوّن Link لجميع التوجيه الداخلي.
س لماذا تحتاج المكوّنات التي تستخدم useRouter إلى تضمين 'use client'؟
ج useRouter هو خطاف React ولا يمكن استخدامه إلا في مكوّنات العميل. لا تملك مكوّنات الخادم وصولاً إلى واجهات برمجة تطبيقات المتصفح (مثل history وlocation)، لذلك يتم تنفيذ الخطافات المتعلقة بالتوجيه على جانب العميل.
س ما الفرق بين router.refresh() وwindow.location.reload()؟
ج router.refresh() هو تحديث ناعم (soft refresh) من Next.js - يعيد طلب حمولة RSC من الخادم، ويحدّث فقط الأجزاء المتغيرة مع الحفاظ على حالة جانب العميل (مثل useState وContext). أما window.location.reload() فيقوم بتحديث كامل للصفحة، مما يتسبب في فقدان جميع الحالات ويتطلب إعادة تحميل كامل لـ JS وCSS.
س هل يعمل التحميل المسبق (prefetch) في Link أيضًا على الأجهزة المحمولة؟
ج نعم. يتم تشغيل prefetch للروابط داخل نطاق العرض، بما في ذلك الأجهزة المحمولة. ومع ذلك، يراعي Next.js استخدام بيانات المستخدم - قد لا يقوم بالتحميل المسبق للصفحات الكبيرة على الشبكات البطيئة (2G/3G). يمكنك أيضًا التحكم يدويًا في ذلك باستخدام prefetch={false}.
س متى يجب استخدام redirect() و<Redirect>؟
ج redirect() يُستخدم على جانب الخادم (Server Actions، Server Components، Route Handlers) لإرسال استجابة 303/307. لإعادة التوجيه الشرطي من جانب العميل، استخدم منطق التحكم في الوصول (عرض رسالة "تم رفض الوصول" + زر تنقل) بدلاً من مكوّن <Redirect>.
س ماذا أفعل إذا تغير عنوان URL لكن محتوى الصفحة ظل كما هو أثناء التنقل؟
ج يحدث هذا عادةً بسبب استخدام نفس React key أو التخزين المؤقت. يمكنك تجربة ما يلي: 1) استخدام key مختلف في مكوّن الصفحة لإجبار إعادة البناء؛ 2) استدعاء router.refresh() لتحديث بيانات الخادم؛ 3) التحقق مما إذا كانت البيانات مخزنة مؤقتًا بواسطة layout.

📖 ملخص


📝 تمارين

  1. سؤال أساسي (⭐): أنشئ خمسة روابط على الصفحة (الرئيسية، حول، اتصل بنا، قائمة المنتجات، تفاصيل المنتج #42)، ولاحظ أي عناوين URL يتم تحميلها مسبقًا في علامة تبويب Network في المتصفح.

  2. تمرين متقدم (⭐⭐): نفّذ سير عمل "تسجيل الدخول → لوحة التحكم": استخدم Server Action للتحقق من تسجيل الدخول؛ إذا نجح، استخدم redirect() للتوجيه إلى لوحة التحكم؛ إذا فشل، استخدم redirect() للعودة إلى صفحة تسجيل الدخول مع تضمين معاملات الخطأ.

  3. تحدٍّ (⭐⭐⭐): نفّذ صفحة قائمة بتمرير لا نهائي. عندما ينقر المستخدم على منتج للانتقال إلى صفحة التفاصيل، استخدم scroll={false} للحفاظ على موضع التمرير؛ عندما يعود المستخدم، يجب أن تبقى القائمة في موضعها السابق. استخدم router.back() لتنفيذ التنقل للخلف.

Web-Tutorial.com

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

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

100%