Next.js: التنقل: Link وواجهة برمجة التطبيقات البرمجية
آخر تحديث: 2026-08-26
التنقل يشبه نظام مترو الأنفاق - هناك خطوط آلية (مكوّنات Link)، وخيارات للتحكم اليدوي (التنقل البرمجي)، وقطارات سريعة للتنقلات الخاطفة (soft navigation)، ومسارات تحويلية (hard navigation).
1. ما ستتعلمه
- الاستخدامات الأساسية لمكوّن
<Link>:prefetch،scroll،replace - التنقل البرمجي باستخدام
useRouter:push،replace،back،forward،refresh - حالات استخدام دالة
redirect()ومكوّن<Redirect> - مبادئ والفروقات بين Soft Navigation و Hard Navigation
- خيارات تنفيذ مؤشرات تحميل التنقل
2. قصة حقيقية لمطور مبتدئ
(1) نقطة الألم: تجربة تنقل سيئة بين الصفحات
تشارلي كان يعمل مؤخرًا على تطوير موقع تجارة إلكترونية باستخدام Next.js واكتشف أن تجربة التنقل سيئة:
"عندما ينقر المستخدم على رابط منتج من الصفحة الرئيسية، تظهر الصفحة فارغة لمدة ثانية واحدة قبل التحميل. وفي كل مرة ينقر فيها المستخدم على 'رجوع'، يتم إعادة تحميل الصفحة بالكامل. بالإضافة إلى ذلك، هناك خطأ - إذا ملأ المستخدم نصف نموذج، ثم نقر بطريق الخطأ على رابط منتج، ثم نقر على 'رجوع'، يتم فقدان جميع بيانات النموذج."
السؤال المحدد:
| المشكلة | التأثير | ملاحظات المستخدم |
|---|---|---|
| شاشة فارغة عند التبديل بين الصفحات | تنقل متعثّر | "يستغرق النقر على الرابط وقتًا طويلاً" |
| فقدان بيانات النموذج | إعادة إدخال البيانات من قبل المستخدم | "ملأت نصفه، لكن عندما عدت، اختفى كل شيء" |
| التحميل المسبق غير ذكي | لا يتم تحميل الصفحات التي يزورها المستخدم كثيرًا مسبقًا | "أبطأ انتقال هو من الصفحة الرئيسية إلى صفحة تفاصيل المنتج" |
| إعادة تعيين موضع التمرير | العودة إلى الأعلى بعد التنقل بين الصفحات في صفحة القائمة | "يجب التمرير للأسفل مرة أخرى في كل مرة" |
(2) الحلول للتنقل في Next.js
مكوّن Link مع الجلب التلقائي + Soft Navigation مع الحفاظ على الحالة + scroll={false} للحفاظ على موضع التمرير.
// تنقل محسّن في صفحة قائمة المنتجات
<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 |
3. مكوّن Link
(1) الاستخدام الأساسي
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
▶ مثال: التنقل الأساسي باستخدام Link
المخرجات:
رسم توضيحي للتنقل من جانب العميل باستخدام مكوّن Link.
// ============================================
// الاستخدام الأساسي لمكوّنات 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>
);
}
المخرجات:
يعرض: روابط تنقل باستخدام مكوّن Link في Next.js للتوجيه من جانب العميل.
المخرجات:
يعرض المتصفح 4 روابط زرقاء:
الرئيسية → /
المنتج 42 → /products/42
إلكترونيات → /products?category=electronics&sort=price
مركز المساعدة → https://help.example.com(رابط خارجي)
(2) prefetch
بشكل افتراضي، يقوم مكوّن Link بالتحميل المسبق للروابط الموجودة داخل نطاق العرض. يختلف سلوك التحميل المسبق بين الخادم والعميل:
| البيئة | prefetch={true} |
prefetch={false} |
|---|---|---|
| التصيير من جانب الخادم | تحميل مسبق لبيانات الصفحة وحمولة RSC | لا تحميل مسبق |
| الصفحة الثابتة | تحميل مسبق للصفحة بالكامل | لا تحميل مسبق |
| داخل نطاق العرض | السلوك الافتراضي | لا تحميل مسبق أبدًا |
▶ مثال: التحكم في سلوك prefetch
المخرجات:
يتم تصيير الصفحة كما هو موصوف أعلاه، مع تحديث واجهة المستخدم بناءً على السلوك الموصوف.
// ============================================
// التحكم في سلوك التحميل المسبق لـ 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>
);
}
المخرجات:
يعرض قائمة المنتجات عبر التكرار.
المحتوى: المنتجات
المخرجات:
بعد تحميل الصفحة، يمكن ملاحظة ذلك في وحدة تحكم الشبكة:
1. /products/1، /products/2، /products/3 تم جلبها مسبقًا (داخل نطاق العرض)
2. /archive لم يتم جلبها مسبقًا(prefetch={false})
3. /checkout تم جلبها مسبقًا(prefetch={true}، حتى لو لم تكن ضمن نطاق العرض)
عندما ينقر المستخدم على الرابط:
- الصفحات التي تم جلبها مسبقًا → عرض فوري(قراءة من ذاكرة التخزين المؤقت)
- الصفحات التي لم يتم جلبها مسبقًا → تُعرض بعد طلب الشبكة
(3) التحكم في التمرير
graph LR
A[تشغيل التنقل] --> B{خاصية scroll}
B -->|scroll=true افتراضي| C[التمرير إلى أعلى الصفحة الجديدة]
B -->|scroll=false| D[الحفاظ على موضع التمرير الحالي]
style C fill:#f8d7da
style D fill:#d4edda
▶ مثال: scroll={false} للحفاظ على موضع التمرير
المخرجات:
رسم توضيحي: تشغيل التنقل؛ التمرير إلى أعلى الصفحة الجديدة؛ الحفاظ على موضع التمرير الحالي.
// ============================================
// قائمة المنتجات + نافذة منبثقة: 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>
);
}
المخرجات:
يعرض قائمة المنتجات عبر التكرار.
المحتوى: كتالوج المنتجات
المخرجات:
1. قام المستخدم بالتمرير إلى الصف الثالث(تم التمرير للأسفل 2000px)
2. النقر على المنتج 15 للانتقال إلى صفحة تفاصيل المنتج
3. النقر على زر الرجوع في المتصفح
4. العودة إلى صفحة القائمة، مع الحفاظ على موضع التمرير عند 2000px(لا يوجد "عودة إلى الأعلى")
5. للمقارنة: إذا لم يتم استخدام scroll={false}، سيتم العودة إلى الأعلى في كل مرة
(4) replace: استبدال السجل
| الإجراء | push (افتراضي) |
replace |
|---|---|---|
| سجل المتصفح | إضافة إدخال جديد | استبدال الإدخال الحالي |
| سلوك زر الرجوع | العودة إلى الصفحة السابقة | الانتقال إلى الصفحة التي تم استبدالها |
| بعد إرسال النموذج | لا يمكن العودة إلى صفحة النموذج | يمكن العودة (تجاوز صفحة النموذج) |
▶ مثال: استخدام replace في سيناريو النموذج
المخرجات:
يقوم المكوّن بتصيير واجهة المستخدم الموصوفة في المتصفح.
// ============================================
// 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>
);
}
المخرجات:
المحتوى: الدفع | تقديم الطلب | بعد تقديم الطلب، سيتجاوز زر الرجوع
المخرجات:
تدفق المستخدم:
1. الرئيسية → سلة التسوق → الدفع → صفحة التأكيد
2. النقر على "تقديم الطلب" في صفحة الدفع
3. يتغير عنوان المتصفح إلى /order-confirmation
4. ينقر المستخدم على زر الرجوع
5. يتجاوز صفحة الدفع، وينتقل مباشرة إلى صفحة سلة التسوق
6. يمنع المستخدمين من العودة بطريق الخطأ إلى صفحة الدفع وإعادة تقديم الطلب
4. التنقل البرمجي باستخدام useRouter
(1) مرجع سريع لواجهة برمجة التطبيقات
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 مؤقتًا |
▶ مثال: التنقل البرمجي
المخرجات:
رسم توضيحي للتنقل من جانب العميل باستخدام مكوّن Link.
// ============================================
// 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>
);
}
المخرجات:
يقوم NavigationBar بتصيير واجهة المستخدم الخاصة به.
المخرجات:
النقر على كل زر:
[الرئيسية] → الانتقال إلى /
[الملف الشخصي] → الانتقال إلى /users/123
[تسجيل الخروج] → استبدال السجل الحالي بـ /login(لا يمكن الرجوع)
[← رجوع] → زر الرجوع في المتصفح
[تقدم] → زر التقدم في المتصفح
[تحديث] → تحديث هذه الصفحة(إعادة تصيير RSC، لا يتسبب في إعادة تحميل الصفحة بالكامل)
▶ مثال: التنقل بعد إرسال النموذج
المخرجات:
يتم تصيير الصفحة كما هو موصوف أعلاه، مع تحديث واجهة المستخدم بناءً على السلوك الموصوف.
// ============================================
// إرسال النموذج: التحقق → الحفظ → التنقل البرمجي
// ============================================
'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>
);
}
المخرجات:
نموذج يحتوي على: name.
المحتوى: إنشاء مشروع
المخرجات:
1. يقوم المستخدم بإدخال اسم المشروع والوصف
2. النقر على زر "إنشاء مشروع"
3. يتغير الزر إلى "جارٍ الإنشاء..."(معطل)
4. بعد 1.5 ثانية (محاكاة تأخير API)
5. انتقال الصفحة إلى /projects/42
6. تحديث بيانات قائمة المشاريع(تشمل المشروع المنشأ حديثًا)
5. redirect() و <Redirect>
(1) نوعان من إعادة التوجيه
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) المقارنة
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) خطة التنفيذ
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. مثال كامل: نظام تنقل للتجارة الإلكترونية
// ============================================
// مثال شامل: نظام تنقل متكامل للتجارة الإلكترونية
// يغطي 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>
);
}
المخرجات المتوقعة:
شريط التنقل(مثبت في الأعلى):
[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={false}.redirect() و<Redirect>؟redirect() يُستخدم على جانب الخادم (Server Actions، Server Components، Route Handlers) لإرسال استجابة 303/307. لإعادة التوجيه الشرطي من جانب العميل، استخدم منطق التحكم في الوصول (عرض رسالة "تم رفض الوصول" + زر تنقل) بدلاً من مكوّن <Redirect>.router.refresh() لتحديث بيانات الخادم؛ 3) التحقق مما إذا كانت البيانات مخزنة مؤقتًا بواسطة layout.📖 ملخص
- مكوّن
<Link>هو حل التنقل المفضل لـ Next.js، ويدعم التحميل المسبق التلقائي والانتقالات من جانب العميل prefetch={true}تحميل مسبق للروابط داخل نطاق العرض،prefetch={false}تعطيل التحميل المسبقscroll={false}يحافظ على موضع التمرير؛ مناسب لصفحات القوائم وصفحات التفاصيلreplaceيتجاوز سجل المتصفح لمنع العودة العرضية من صفحات النماذجuseRouterيوفر تنقلًا برمجيًا:push،replace،back،forward،refreshredirect()للاستخدام على جانب الخادم؛ يرسل استجابة إعادة توجيه 303/307- Soft Navigation يحافظ على layout والحالة، بينما Hard Navigation يقوم بتحديث الصفحة بالكامل
router.refresh()هو ميزة Soft Navigation خاصة: تحديث بيانات RSC دون تغيير عنوان URL
📝 تمارين
-
سؤال أساسي (⭐): أنشئ خمسة روابط على الصفحة (الرئيسية، حول، اتصل بنا، قائمة المنتجات، تفاصيل المنتج #42)، ولاحظ أي عناوين URL يتم تحميلها مسبقًا في علامة تبويب Network في المتصفح.
-
تمرين متقدم (⭐⭐): نفّذ سير عمل "تسجيل الدخول → لوحة التحكم": استخدم Server Action للتحقق من تسجيل الدخول؛ إذا نجح، استخدم
redirect()للتوجيه إلى لوحة التحكم؛ إذا فشل، استخدمredirect()للعودة إلى صفحة تسجيل الدخول مع تضمين معاملات الخطأ. -
تحدٍّ (⭐⭐⭐): نفّذ صفحة قائمة بتمرير لا نهائي. عندما ينقر المستخدم على منتج للانتقال إلى صفحة التفاصيل، استخدم
scroll={false}للحفاظ على موضع التمرير؛ عندما يعود المستخدم، يجب أن تبقى القائمة في موضعها السابق. استخدمrouter.back()لتنفيذ التنقل للخلف.