استخدام Vue Router للتنقل بين الصفحات
عندما تبدأ في بناء تطبيق بواجهة حديثة باستخدام Vue، ستلاحظ سريعًا أن التنقل بين الصفحات ليس مجرد انتقال بصري من شاشة إلى أخرى، بل هو جزء أساسي من شعور المستخدم بأن التطبيق “حي” وسريع ومنظم. هنا يأتي دور Vue Router، وهو الحل الرسمي تقريبًا لتنظيم الانتقال بين المسارات داخل تطبيقات Vue، خصوصًا في تطبيقات الصفحة الواحدة SPA. بدلًا من إعادة تحميل الصفحة كاملة في كل مرة، يسمح لك Vue Router بتبديل المحتوى بسلاسة، مع الحفاظ على الحالة، وتقليل وقت الانتظار، وتقديم تجربة أقرب إلى التطبيقات الاحترافية التي اعتاد عليها المستخدمون اليوم.
الجميل في Vue Router أنه لا يقتصر على “إظهار صفحة هنا وإخفاء صفحة هناك”، بل يمنحك نظامًا متكاملًا لإدارة المسارات، والتعامل مع المعاملات، وتنفيذ الحماية قبل الدخول إلى بعض الصفحات، والتحكم في التمرير، وتطبيق التخطيط المتداخل، وحتى تحميل الصفحات بشكل كسول لتحسين الأداء. وهذا ما يجعل فهمه من البداية مهمًا جدًا، ليس فقط للمبتدئ الذي يبني أول مشروع له، بل أيضًا للمطور الذي يريد ترتيب تطبيقه بشكل قابل للتوسع والنمو دون فوضى لاحقًا.
في هذا المقال، سنمشي خطوة بخطوة مع Vue Router كما لو أننا نبني مشروعًا حقيقيًا من الصفر. لن نتوقف عند التعريفات النظرية فقط، بل سنكتب إعدادًا عمليًا، وننشئ مسارات متعددة، ونربطها بالمكونات، ونتعامل مع params وquery، ونفهم الفرق بين router-link و router-view، ونستخدم التنقل البرمجي، ونعالج الحالات الشائعة التي تربك الكثير من المطورين في أول مرة يتعاملون فيها مع التوجيه داخل Vue. ستجد هنا أسلوبًا عمليًا، هادئًا، وبلغة بسيطة قدر الإمكان، مع أمثلة كافية لتخرج وأنت قادر على بناء نظام تنقل متين في أي مشروع Vue.
ما هو Vue Router ولماذا نحتاجه؟
فكرة التوجيه في التطبيقات الحديثة تبدو بسيطة من الخارج، لكنها مهمة جدًا من الداخل. في الموقع التقليدي متعدد الصفحات، كل نقرة على رابط تؤدي غالبًا إلى طلب جديد من الخادم وتحميل صفحة HTML جديدة بالكامل. أما في تطبيقات SPA المبنية بـ Vue، فالمبدأ مختلف: التطبيق يُحمّل مرة واحدة تقريبًا، ثم يتولى JavaScript إدارة التنقل داخل الواجهة دون إعادة تحميل الصفحة كلها. Vue Router هو الذي ينظم هذا الانتقال الداخلي بين “الصفحات” أو بالأصح بين “المسارات” والواجهات المرتبطة بها.
لو تخيلت التطبيق مثل مدينة، فالمسارات هي الشوارع، والمكونات هي المباني الموجودة على تلك الشوارع، وVue Router هو نظام الإشارات الذي يحدد لك أين تذهب، ومتى تصل، وماذا يُعرض لك في كل نقطة. هذه الصورة الذهنية مفيدة جدًا، لأنك ستدرك أن المسار ليس مجرد عنوان في شريط المتصفح، بل جزء من هيكل التطبيق وتجربة المستخدم معًا.
عندما لا تستخدم Router بشكل صحيح، يبدأ المشروع في التحول إلى مزيج مربك من الحالات والمكوّنات غير المرتبة. قد ترى صفحات تُعرض داخل صفحات، أو روابط لا تعود منطقية، أو تنقلًا يعتمد على v-if و component switching بشكل بدائي، وهذا عادة يجعل الصيانة أصعب بكثير. أما باستخدام Vue Router بالطريقة الصحيحة، تصبح كل صفحة أو شاشة مرتبطة بمسار واضح، وتكون بنية التطبيق أسهل في الفهم والتطوير، خصوصًا عندما يعمل أكثر من شخص على نفس المشروع.
تثبيت Vue Router داخل مشروع Vue
إذا كنت تستخدم Vue 3، فغالبًا ستتعامل مع Vue Router 4. التثبيت بسيط جدًا، وغالبًا لا يحتاج أكثر من سطر واحد. داخل مشروعك، نفّذ:
npm install vue-router
أو باستخدام Yarn:
yarn add vue-router
أو pnpm:
pnpm add vue-router
بعد التثبيت، تأتي الخطوة الأهم: إنشاء ملف خاص بالراوتر وتنظيم المسارات فيه. من الأفضل ألا تضع كل شيء في main.js لأن ذلك سيجعل الكود متكدسًا بسرعة. الفصل بين إعداد التطبيق وإعداد التوجيه يعطيك هيكلًا أنظف وأسهل للصيانة.
إعداد Router من الصفر في Vue 3
لنبدأ بمثال عملي بسيط. لنفترض أن لديك تطبيقًا يحتوي على ثلاث صفحات: الصفحة الرئيسية، صفحة “من نحن”، وصفحة “تواصل معنا”. سننشئ ملفات المكونات أولًا، ثم نربطها بالمسارات.
هيكل ملفات مبسط قد يكون هكذا:
src/
├─ views/
│ ├─ HomeView.vue
│ ├─ AboutView.vue
│ └─ ContactView.vue
├─ router/
│ └─ index.js
├─ App.vue
└─ main.js
ملف main.js:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
الآن ملف router/index.js:
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'
import ContactView from '../views/ContactView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: AboutView
},
{
path: '/contact',
name: 'contact',
component: ContactView
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
هذا الإعداد هو نقطة البداية الأساسية. createRouter ينشئ الراوتر، وcreateWebHistory يجعله يستخدم تاريخ المتصفح بشكل جميل ونظيف في العنوان، بدلًا من أسلوب hash القديم الذي يظهر علامة # في الرابط. أغلب المشاريع الحديثة تفضّل createWebHistory ما لم تكن لديك حاجة خاصة لاستخدام التاريخ المبني على hash.
أما في App.vue، فغالبًا ستضع شريط تنقل ثابت، ثم router-view لعرض الصفحة الحالية:
<template>
<div class="app">
<nav class="main-nav">
<RouterLink to="/">الرئيسية</RouterLink>
<RouterLink to="/about">من نحن</RouterLink>
<RouterLink to="/contact">تواصل معنا</RouterLink>
</nav>
<main>
<RouterView />
</main>
</div>
</template>
هنا نلاحظ عنصرين مهمين جدًا: RouterLink و RouterView. الأول هو الرابط الذكي الذي يتعامل مع التوجيه داخليًا دون إعادة تحميل الصفحة، والثاني هو المكان الذي تُعرض فيه المكوّنات المطابقة للمسار الحالي. إذا أردت أن تفهم Vue Router جيدًا، فافهم هذين العنصرين أولًا، لأنهما العمود الفقري للتنقل.
الفرق بين RouterLink و a العادي
قد يسأل أحدهم: لماذا لا أستخدم <a href="/about"> وانتهى الأمر؟ الجواب بسيط: يمكنك ذلك، لكنك ستخسر مزايا تطبيق SPA. الرابط العادي يرسل المتصفح إلى الصفحة كأنها زيارة جديدة، وقد يؤدي إلى تحميل كامل للواجهة من جديد إذا لم يُضبط الخادم بالشكل المناسب. أما RouterLink فيتحدث مع Vue Router مباشرة، فيغيّر المسار دون إعادة تحميل الصفحة، ويحافظ على الأداء والسلاسة والحالة الداخلية للتطبيق.
هناك أيضًا جانب صغير لكنه مهم جدًا: RouterLink يفهم حالة الرابط النشط، ويمكنه إضافة classes تلقائيًا مثل router-link-active و router-link-exact-active. هذه ميزة مهمة جدًا في القوائم الجانبية أو شريط التنقل، لأنها تسمح لك بتمييز الصفحة الحالية بصريًا دون كتابة منطق يدوي.
مثال:
<template>
<nav>
<RouterLink to="/" class="menu-link">الرئيسية</RouterLink>
<RouterLink to="/about" class="menu-link">من نحن</RouterLink>
</nav>
</template>
ويمكنك تخصيص الشكل النشط عبر CSS:
.menu-link {
margin-right: 12px;
text-decoration: none;
color: #444;
}
.router-link-active {
font-weight: bold;
color: #42b883;
}
ملاحظة عملية صغيرة
أحيانًا يرى المبتدئون أن الرابط يغيّر العنوان لكن الصفحة لا تتغير، أو العكس. في أغلب الأحيان يكون السبب أنهم نسوا وضع <RouterView /> في المكان الصحيح، أو أنهم لم يربطوا المكونات بالمسارات في ملف router. كل شيء في Vue Router يعتمد على هذا الربط الواضح: المسار هنا، والمكوّن هناك، وRouterView هو المسرح الذي يقدمه للمستخدم.
كتابة الصفحات الأساسية
لنكتب الآن المكونات الثلاثة بطريقة بسيطة:
HomeView.vue
<template>
<section>
<h1>الصفحة الرئيسية</h1>
<p>مرحبًا بك في التطبيق. هذه هي الصفحة الأولى التي يراها المستخدم عند الدخول.</p>
</section>
</template>
AboutView.vue
<template>
<section>
<h1>من نحن</h1>
<p>هذه الصفحة تعرض معلومات عن الفريق أو المشروع أو الشركة.</p>
</section>
</template>
ContactView.vue
<template>
<section>
<h1>تواصل معنا</h1>
<p>يمكنك استخدام هذه الصفحة لعرض نموذج التواصل أو معلومات الدعم.</p>
</section>
</template>
قد تبدو هذه المكونات بدائية جدًا، لكن الفكرة هنا هي أن تفهم الأساس قبل أن ننتقل إلى التنظيم الأكثر تقدمًا. في المشاريع الحقيقية، هذه الصفحات قد تحتوي على مئات الأسطر، ونماذج، وجداول، ومكونات فرعية، ولكن المنطق نفسه يبقى واحدًا.
استخدام أسماء المسارات بدلًا من المسارات النصية
من أفضل الممارسات أن تمنح كل Route اسمًا name، ثم تستخدم هذا الاسم في التنقل بدلًا من الاعتماد دائمًا على النص الكامل للمسار. هذا يجعل الكود أكثر مرونة، لأنك لو غيّرت المسار لاحقًا فلن تضطر لتعديل كل الروابط يدويًا.
مثال على التنقل عبر الاسم:
<RouterLink :to="{ name: 'about' }">من نحن</RouterLink>
وفي ملف الراوتر:
{
path: '/about',
name: 'about',
component: AboutView
}
لماذا هذا مهم؟ لأنك عندما تبني مشروعًا كبيرًا، قد تتغير المسارات أكثر من مرة. ربما كنت تستخدم /about-us ثم قررت لاحقًا أن تجعلها /company/about. لو كنت تستخدم الاسم، يصبح التعديل أسهل وأقل خطورة. هذا النوع من التفكير يوفر عليك كثيرًا من الوقت عندما يبدأ المشروع في النمو.
التنقل البرمجي باستخدام useRouter
ليس كل تنقل في التطبيق يحدث عبر الضغط على رابط ظاهر. أحيانًا تحتاج إلى الانتقال برمجيًا بعد حدث معين، مثل إرسال نموذج تسجيل، أو تسجيل الدخول بنجاح، أو الضغط على زر مخصص، أو انتهاء عملية حفظ البيانات. هنا يأتي دور useRouter.
مثال داخل مكوّن:
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const goToHome = () => {
router.push('/')
}
</script>
<template>
<button @click="goToHome">الذهاب إلى الرئيسية</button>
</template>
يمكنك أيضًا الانتقال باسم المسار:
router.push({ name: 'contact' })
أو مع تمرير بيانات:
router.push({ name: 'user-profile', params: { id: 15 } })
أو مع query:
router.push({ path: '/search', query: { q: 'vue router' } })
هنا تظهر مرونة Vue Router الحقيقية. أنت لا تتعامل مع التنقل كأنه مجرد “رابط”، بل كعملية منظمة يمكن تنفيذها من أي نقطة في التطبيق، وفق شروط معينة.
فهم params و query بوضوح
هذا جزء يربك الكثير من المطورين في البداية، لكنه سهل جدًا عندما تفهمه بشكل عملي.params تُستخدم عادة عندما يكون لديك جزء من المسار نفسه يمثل قيمة متغيرة، مثل رقم المستخدم أو اسم المقال.
أما query فهي المعاملات التي تأتي بعد علامة ? في الرابط، وغالبًا تُستخدم للفلترة أو البحث أو الترتيب أو خيارات العرض.
مثال على params:
{
path: '/users/:id',
name: 'user-profile',
component: () => import('../views/UserProfileView.vue')
}
عند زيارة:
/users/12
يمكنك داخل المكوّن قراءة id.
مثال على UserProfileView.vue:
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
</script>
<template>
<section>
<h1>الملف الشخصي للمستخدم</h1>
<p>رقم المستخدم: {{ route.params.id }}</p>
</section>
</template>
أما query، فمثالها:
/search?q=vue&page=2
وفي المكوّن:
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
</script>
<template>
<section>
<h1>نتائج البحث</h1>
<p>الكلمة المفتاحية: {{ route.query.q }}</p>
<p>الصفحة الحالية: {{ route.query.page }}</p>
</section>
</template>
الفرق العملي بينهما مهم جدًا. إذا كانت القيمة جزءًا من هوية الصفحة نفسها، فغالبًا استخدم params. وإذا كانت مجرد خيار أو فلتر أو حالة عرض، فغالبًا استخدم query.
إنشاء مسارات ديناميكية
المسارات الديناميكية هي القلب الحقيقي لتطبيقات كثيرة، خصوصًا عندما تتعامل مع المستخدمين، المنتجات، المقالات، الطلبات، أو أي شيء يتغير من عنصر إلى آخر. بدلًا من إنشاء صفحة مستقلة لكل مستخدم، يمكنك إنشاء مسار واحد ذكي يستقبل القيمة المتغيرة ويعرض المحتوى المناسب.
مثال:
{
path: '/products/:slug',
name: 'product-details',
component: () => import('../views/ProductDetailsView.vue')
}
في ProductDetailsView.vue:
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const slug = computed(() => route.params.slug)
</script>
<template>
<section>
<h1>تفاصيل المنتج</h1>
<p>المنتج الحالي: {{ slug }}</p>
</section>
</template>
في المشاريع الواقعية، لن تعرض slug فقط، بل ستستخدمه لجلب البيانات من API. وهنا يصبح الراوتر جزءًا من تدفق البيانات داخل التطبيق، وليس مجرد أداة UI فقط.
الجلب حسب المعرف بعد تغيير المسار
من الأخطاء الشائعة أن يقرأ المطور route.params.id مرة واحدة ثم يعتقد أن القيمة ستتحدث دائمًا تلقائيًا في كل الحالات. أحيانًا عندما ينتقل المستخدم من /users/1 إلى /users/2 داخل نفس المكوّن، قد لا يُعاد إنشاء المكوّن بالكامل، لذا يجب الانتباه إلى التغييرات.
مثال عملي:
<script setup>
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const userData = ref(null)
const loadUser = async (id) => {
// افترض هنا أنك تجلب البيانات من API
userData.value = { id, name: `User ${id}` }
}
watch(
() => route.params.id,
(newId) => {
if (newId) {
loadUser(newId)
}
},
{ immediate: true }
)
</script>
<template>
<section>
<h1>مستخدم</h1>
<pre>{{ userData }}</pre>
</section>
</template>
هذا النمط مهم جدًا لأنك لا تريد أن يتجمد العرض عند أول قيمة فقط. في التطبيقات التفاعلية، المسار نفسه قد يتغير عدة مرات من دون مغادرة الصفحة بالكامل، ولذلك يجب التعامل مع watch أو onBeforeRouteUpdate بذكاء.
استخدام الحراسة Guards لحماية الصفحات
أحد أقوى استخدامات Vue Router هو التحكم في الوصول إلى الصفحات قبل عرضها. قد ترغب في منع المستخدم غير المسجل من دخول لوحة التحكم، أو منع المستخدم من الانتقال إلى صفحة معينة إذا لم يكمل خطوة مطلوبة، أو التأكد من صلاحية التوثيق قبل عرض بيانات حساسة.
في Vue Router توجد عدة أنواع من الحراسة، لكن أكثرها شيوعًا هو beforeEach على مستوى الراوتر.
مثال:
router.beforeEach((to, from, next) => {
const isLoggedIn = localStorage.getItem('token')
if (to.meta.requiresAuth && !isLoggedIn) {
next({ name: 'login' })
} else {
next()
}
})
ولكي يعمل هذا، يمكننا استخدام meta داخل المسار:
{
path: '/dashboard',
name: 'dashboard',
component: () => import('../views/DashboardView.vue'),
meta: { requiresAuth: true }
}
هذا الأسلوب ممتاز جدًا لأنه يفصل بين تعريف المسار وبين المنطق الشرطي المرتبط به. بدلًا من كتابة if داخل كل مكوّن، أنت تضع المعلومة في meta، ثم الراوتر يتولى القرار. هذه طريقة أنظف بكثير وأكثر قابلية للتوسع.
مثال أكثر واقعية
لنفترض أن عندك ثلاثة أنواع من الصفحات: صفحات عامة، وصفحات تحتاج تسجيل دخول، وصفحات تحتاج صلاحية مدير. يمكنك تنظيم ذلك عبر meta:
{
path: '/admin',
name: 'admin',
component: () => import('../views/AdminView.vue'),
meta: { requiresAuth: true, role: 'admin' }
}
ثم داخل guard:
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
const userRole = localStorage.getItem('role')
if (to.meta.requiresAuth && !token) {
next({ name: 'login' })
return
}
if (to.meta.role && to.meta.role !== userRole) {
next({ name: 'forbidden' })
return
}
next()
})
طبعًا في المشاريع الاحترافية قد تكون آلية التحقق أكثر تعقيدًا، لكن المبدأ نفسه: meta يحمل بيانات عن المسار، والـ guard يطبق القواعد قبل الوصول.
المسارات المتداخلة Nested Routes
أحيانًا لا تكون الصفحة وحدة مستقلة بالكامل، بل تكون جزءًا من صفحة أكبر تحتوي على أقسام فرعية. مثل لوحة تحكم فيها “الملف الشخصي”، “الإعدادات”، “الفواتير”، وكلها تشترك في نفس التخطيط العام. هنا تظهر قيمة المسارات المتداخلة.
مثال:
{
path: '/dashboard',
component: () => import('../views/DashboardLayout.vue'),
children: [
{
path: '',
name: 'dashboard-home',
component: () => import('../views/DashboardHome.vue')
},
{
path: 'settings',
name: 'dashboard-settings',
component: () => import('../views/DashboardSettings.vue')
},
{
path: 'billing',
name: 'dashboard-billing',
component: () => import('../views/DashboardBilling.vue')
}
]
}
في DashboardLayout.vue:
<template>
<div class="dashboard-layout">
<aside>
<RouterLink :to="{ name: 'dashboard-home' }">الرئيسية</RouterLink>
<RouterLink :to="{ name: 'dashboard-settings' }">الإعدادات</RouterLink>
<RouterLink :to="{ name: 'dashboard-billing' }">الفواتير</RouterLink>
</aside>
<section>
<RouterView />
</section>
</div>
</template>
هذه البنية جميلة جدًا لأنك تحتفظ بالـ layout العام، وفي الوقت نفسه تغيّر المحتوى الداخلي فقط. وهذا ما يجعل تجربة المستخدم أكثر اتساقًا، ويمنع تكرار الكود في كل صفحة فرعية.
إعادة التوجيه Redirect
في بعض الأحيان تريد أن توجه المستخدم تلقائيًا من مسار إلى آخر. قد يحدث هذا عندما تنقل بنية التطبيق، أو عندما تريد إرسال الزائر من / إلى /home، أو عندما يكون لديك route قديم وتحتاج إلى الحفاظ على التوافق.
مثال:
{
path: '/',
redirect: '/home'
}
أو باستخدام اسم route:
{
path: '/old-about',
redirect: { name: 'about' }
}
هذا مهم جدًا في المشاريع الحقيقية، لأن المسارات لا تبقى ثابتة دائمًا. إعادة التوجيه تحفظ التجربة وتحميك من الروابط المكسورة أو الصفحات القديمة.
صفحة 404 أو Not Found
لا يوجد تطبيق احترافي بدون صفحة 404. عندما يدخل المستخدم مسارًا غير موجود، يجب ألا يواجه شاشة فارغة أو خطأ غامضًا. بل يجب أن يرى صفحة واضحة تشرح أن الصفحة غير موجودة وتقترح عليه الرجوع أو الذهاب للرئيسية.
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('../views/NotFoundView.vue')
}
وفي NotFoundView.vue:
<template>
<section>
<h1>الصفحة غير موجودة</h1>
<p>يبدو أن الرابط الذي حاولت الوصول إليه غير صحيح أو أن الصفحة تم نقلها.</p>
<RouterLink to="/">العودة إلى الصفحة الرئيسية</RouterLink>
</section>
</template>
وجود صفحة 404 ليس مجرد تحسين تجميلي، بل جزء من جودة التجربة ومن التنظيم الداخلي للتطبيق.
التحميل الكسول Lazy Loading
مع نمو التطبيق، سيصبح من غير العملي تحميل كل الصفحات دفعة واحدة. هنا يأتي دور التحميل الكسول، وهو أسلوب يجعل Vue Router يحمل الصفحة فقط عندما يحتاجها المستخدم فعلًا. هذا يساعد في تحسين الأداء الأولي وتقليل حجم الحزمة الأساسية.
بدلًا من:
import AboutView from '../views/AboutView.vue'
استخدم:
component: () => import('../views/AboutView.vue')
مثال كامل:
const routes = [
{
path: '/',
name: 'home',
component: () => import('../views/HomeView.vue')
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue')
}
]
هذا الأسلوب مهم جدًا في التطبيقات الكبيرة. تخيل أن لديك لوحة تحكم تحتوي على عشرات الصفحات، وبعضها لا يزوره المستخدم إلا نادرًا. لماذا تحمّلها من البداية؟ التحميل الكسول يوفّر موارد المتصفح ويجعل التطبيق يشعر بالسرعة منذ الوهلة الأولى.
التحكم في التمرير Scroll Behavior
من التفاصيل الجميلة في Vue Router أنك تستطيع التحكم في المكان الذي يصل إليه التمرير بعد التنقل. في بعض الحالات، تريد عند الانتقال إلى صفحة جديدة أن يعود المستخدم إلى أعلى الصفحة. وفي حالات أخرى، تريد أن يحافظ التطبيق على موضعه أو يعود إلى موضع محفوظ.
مثال:
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
}
})
هذه الخاصية تبدو صغيرة لكنها تحدث فرقًا كبيرًا في تجربة المستخدم، خاصة في الصفحات الطويلة أو المدونات أو المتاجر الإلكترونية. الانتقال إلى صفحة جديدة دون إعادة ضبط التمرير قد يربك المستخدم، بينما التحكم الجيد فيه يجعل التطبيق أكثر سلاسة.
التعامل مع Param وQuery أثناء التنقل عبر RouterLink
يمكنك تمرير المعاملات مباشرة عبر RouterLink، وهذا أسلوب أنيق جدًا. مثلًا:
<RouterLink :to="{ name: 'user-profile', params: { id: 7 } }">
عرض ملف المستخدم
</RouterLink>
أو:
<RouterLink :to="{ path: '/search', query: { q: 'vue router', page: 1 } }">
البحث
</RouterLink>
هذه الطريقة أكثر وضوحًا من تركيب الروابط يدويًا كسلاسل نصية طويلة. كما أنها تقلل الأخطاء، وتجعل الكود أقرب إلى المنطق من كونه مجرد نصوص متفرقة.
استخدام Route Meta لعرض عناوين مختلفة أو خصائص خاصة
meta ليس فقط للحماية. يمكنك أيضًا استخدامه لتخزين معلومات مفيدة عن الصفحة، مثل العنوان، أو نوع التخطيط، أو ما إذا كانت الصفحة تحتاج شريطًا جانبيًا، أو ما إذا كان ينبغي إخفاء الهيدر.
مثال:
{
path: '/login',
name: 'login',
component: () => import('../views/LoginView.vue'),
meta: {
title: 'تسجيل الدخول',
layout: 'auth'
}
}
ثم يمكنك قراءة هذه القيمة وتحديث عنوان الصفحة:
router.afterEach((to) => {
document.title = to.meta.title || 'تطبيقي'
})
هذه إضافة صغيرة لكنها تضيف احترافية كبيرة. كثير من التطبيقات الجيدة تهمل هذه النقطة رغم أهميتها في تنظيم الصفحة وتحسين الانطباع العام.
التنقل بعد نجاح تسجيل الدخول
أحد أكثر السيناريوهات استخدامًا هو بعد نجاح تسجيل الدخول. عادة بعد التحقق من بيانات المستخدم، تريد نقله مباشرة إلى لوحة التحكم أو الصفحة التي كان يحاول الوصول إليها.
مثال:
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const email = ref('')
const password = ref('')
const login = async () => {
// هنا يتم التحقق من البيانات من API
const success = true
if (success) {
localStorage.setItem('token', 'fake-token')
router.push({ name: 'dashboard' })
}
}
</script>
<template>
<form @submit.prevent="login">
<input v-model="email" type="email" placeholder="البريد الإلكتروني" />
<input v-model="password" type="password" placeholder="كلمة المرور" />
<button type="submit">دخول</button>
</form>
</template>
في المشاريع الحقيقية قد تضيف أيضًا منطقًا لإرجاع المستخدم إلى الصفحة التي حاول فتحها قبل تسجيل الدخول. هذا يعطي تجربة أفضل بكثير من إرسال الجميع دائمًا إلى نفس الوجهة.
إرجاع المستخدم إلى الصفحة التي كان يريدها
تخيل أن المستخدم أراد فتح /dashboard لكنه غير مسجل، فحوّلته إلى /login. بعد نجاح الدخول، سيكون من الجميل أن يرجع مباشرة إلى /dashboard بدلًا من الصفحة الرئيسية.
يمكنك فعل ذلك بهذه الطريقة:
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.meta.requiresAuth && !token) {
next({
name: 'login',
query: { redirect: to.fullPath }
})
} else {
next()
}
})
ثم بعد تسجيل الدخول:
const redirectPath = route.query.redirect || '/dashboard'
router.push(redirectPath)
هذه لمسة مهمة جدًا. ربما لا ينتبه لها المبتدئ في البداية، لكنها من التفاصيل التي تجعل التطبيق “يفهم” المستخدم بدلًا من أن يفرض عليه سلوكًا صارمًا.
التعامل مع الأخطاء والمشاكل الشائعة
من الطبيعي جدًا أن تواجه بعض اللبس في البداية عند استخدام Vue Router. مثلًا، قد تكتب RouterView ولا يظهر شيء، أو تنسى إضافة router إلى createApp, أو تخلط بين name وpath, أو تستخدم params مع path بطريقة غير صحيحة. هذه أمور تحدث كثيرًا، ولا تعني أنك لا تفهم Vue؛ فقط تعني أنك لم تبنِ ذاكرة عملية بعد.
من المشاكل الشائعة أيضًا:
أنك تنتقل إلى route ديناميكي باستخدام path وتنسى أنك تحتاج قيمة params، أو أن تستخدم params مع path دون أن يطابق الشكل المتوقّع، أو أن تظن أن query و params نفس الشيء. لذلك، من المفيد جدًا أن تحفظ هذا التفريق: params جزء من المسار، وquery جزء إضافي بعد علامة ?.
كذلك، عند استخدام guards، يجب أن تنتبه إلى عدم إنشاء حلقات تحويل لا نهائية. مثال سيئ: أن تمنع صفحة login نفسها عن غير المسجلين، فيتم تحويل المستخدم إليها دائمًا من جديد، وهكذا تدخل في دائرة لا تنتهي. لذلك، اكتب الشروط بدقة، وفكر دائمًا: “هل المسار الحالي نفسه جزء من الاستثناءات؟”
بناء layout احترافي مع RouterView
في المشاريع المتقدمة، قد يكون لديك أكثر من تخطيط: تخطيط للصفحات العامة، تخطيط للوحة التحكم، وتخطيط لصفحات المصادقة. Vue Router يسمح لك بتنظيم هذا بطريقة مريحة جدًا عبر layouts وnested routes.
مثال لهيكل عام:
const routes = [
{
path: '/',
component: () => import('../layouts/PublicLayout.vue'),
children: [
{ path: '', name: 'home', component: () => import('../views/HomeView.vue') },
{ path: 'about', name: 'about', component: () => import('../views/AboutView.vue') }
]
},
{
path: '/auth',
component: () => import('../layouts/AuthLayout.vue'),
children: [
{ path: 'login', name: 'login', component: () => import('../views/LoginView.vue') },
{ path: 'register', name: 'register', component: () => import('../views/RegisterView.vue') }
]
}
]
هذا الترتيب يساعدك على فصل الواجهات بشكل نظيف. كل مجموعة صفحات تشترك في الشكل العام نفسه، بينما يبقى المحتوى الداخلي قابلًا للتبديل بسهولة. وفي الشركات أو المشاريع الكبيرة، هذا النوع من التنظيم ضروري تقريبًا، لأنه يحافظ على الكود من التشتت.
إدارة الحالة عند التبديل بين الصفحات
Vue Router لا يدير الحالة بنفسه، لكنه يرتبط بها بشكل قوي جدًا. أحيانًا عندما تنتقل من صفحة إلى أخرى، تريد أن يبقى جزء من الحالة، وأحيانًا تريد التخلص منها بالكامل. فهم هذا مهم حتى لا تظن أن الراوتر وحده مسؤول عن كل شيء.
مثلًا، إذا كان لديك صفحة بحث وتريد الحفاظ على النتائج عند العودة من صفحة التفاصيل، فقد تحتاج إلى تخزين الحالة في Pinia أو في route query أو في ذاكرة مؤقتة. أما إذا كنت تريد إعادة تحميل البيانات كل مرة، فببساطة استخدم lifecycle أو watch على route. الفكرة أن الراوتر يحدد “أين أنا الآن؟”، أما الحالة فتحدد “ما الذي أعرفه عن هذا المكان؟”.
استخدام Vue Router مع Composition API
في Vue 3، أغلب المشاريع الحديثة تستخدم Composition API، وهذا يجعل التكامل مع Vue Router جميلًا ومباشرًا جدًا.
مثال:
<script setup>
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const openDetails = () => {
router.push({
name: 'product-details',
params: { slug: 'awesome-product' }
})
}
</script>
<template>
<div>
<p>المسار الحالي: {{ route.fullPath }}</p>
<button @click="openDetails">فتح التفاصيل</button>
</div>
</template>
وجود useRoute وuseRouter يجعل الوصول إلى التوجيه مباشرًا جدًا من داخل أي مكوّن تقريبًا. وهذا يساعدك على كتابة كود أنظف وأقل اعتمادًا على الخيارات القديمة.
متى أستخدم router-link ومتى أستخدم router.push؟
هذا سؤال عملي جدًا. استخدم router-link عندما يكون عندك رابط ظاهر للمستخدم، مثل عناصر التنقل والقائمة والشريط الجانبي والروابط داخل النص. استخدم router.push عندما يكون التنقل نتيجة حدث برمجي، مثل نجاح تسجيل الدخول أو الضغط على زر أو نتيجة تلقائية بعد إكمال خطوة.
بعبارة أبسط:
إذا كان التنقل جزءًا من الواجهة نفسها، فغالبًا RouterLink.
إذا كان التنقل جزءًا من منطق التطبيق، فغالبًا router.push.
هذا التفريق الصغير يضعك على الطريق الصحيح منذ البداية، ويجعل الكود أكثر وضوحًا لمن يقرأه بعدك.
مثال عملي أكثر قربًا من المشاريع الحقيقية
لنفرض أننا نبني متجرًا بسيطًا. سنحتاج إلى صفحة رئيسية، صفحة قائمة المنتجات، صفحة تفاصيل المنتج، صفحة تسجيل الدخول، ولوحة تحكم إدارية، وصفحة 404.
router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'home',
component: () => import('../views/HomeView.vue')
},
{
path: '/products',
name: 'products',
component: () => import('../views/ProductsView.vue')
},
{
path: '/products/:slug',
name: 'product-details',
component: () => import('../views/ProductDetailsView.vue')
},
{
path: '/login',
name: 'login',
component: () => import('../views/LoginView.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('../views/DashboardView.vue'),
meta: { requiresAuth: true }
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('../views/NotFoundView.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior() {
return { top: 0 }
}
})
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.meta.requiresAuth && !token) {
next({ name: 'login', query: { redirect: to.fullPath } })
return
}
next()
})
export default router
وفي ProductsView.vue:
<template>
<section>
<h1>المنتجات</h1>
<ul>
<li>
<RouterLink :to="{ name: 'product-details', params: { slug: 'laptop-pro' } }">
لابتوب برو
</RouterLink>
</li>
<li>
<RouterLink :to="{ name: 'product-details', params: { slug: 'smart-phone-x' } }">
هاتف ذكي X
</RouterLink>
</li>
</ul>
</section>
</template>
وفي ProductDetailsView.vue:
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const slug = computed(() => route.params.slug)
</script>
<template>
<section>
<h1>تفاصيل المنتج</h1>
<p>المنتج الحالي هو: {{ slug }}</p>
</section>
</template>
هذا المثال قد يبدو بسيطًا، لكنه يضم تقريبًا معظم الأفكار الأساسية التي تحتاجها: مسارات عادية، مسار ديناميكي، صفحة محمية، إعادة توجيه عند الحاجة، و404، وscroll behavior. إذا فهمت هذا المثال جيدًا، فأنت بالفعل قطعت شوطًا ممتازًا.
نصائح عملية من واقع الاستخدام
عند بناء الراوتر في مشروع حقيقي، حاول أن تفكر مبكرًا في بنية المسارات. لا تجعل كل شيء عشوائيًا منذ البداية، لأن إعادة تنظيم routes لاحقًا تصبح أصعب مع الوقت. كذلك، استخدم أسماء واضحة للمسارات، واجعل أسماء المكوّنات والمجلدات منسقة مع الغرض الحقيقي من الصفحة. على سبيل المثال، UserProfileView.vue أفضل من اسم عام لا يوضح شيئًا.
من الأفضل أيضًا أن تفصل بين صفحات العرض العامة وواجهات المصادقة ولوحة التحكم. هذا الفصل لا يفيد فقط في تنظيم الراوتر، بل يساعد أيضًا في بناء تخطيط بصري موحد لكل مجموعة. ولا تنسَ التحميل الكسول؛ فهو من أبسط التحسينات التي تقدم فائدة ملموسة جدًا في الأداء.
ومن النصائح التي قد تبدو صغيرة لكنها مهمة: لا تفرط في وضع منطق معقد داخل المكوّنات نفسها إذا كان القرار يخص الانتقال أو الحماية. اجعل الراوتر مسؤولًا عن التوجيه، واجعل المكوّن مسؤولًا عن العرض، واجعل الخدمات مسؤولة عن جلب البيانات. هذا الفصل بين المسؤوليات هو ما يبقي المشروع قابلًا للفهم بعد أشهر، وليس فقط عند إنشائه لأول مرة.
أخطاء شائعة يجب تجنبها
هناك مجموعة أخطاء تتكرر كثيرًا، خاصة عند من يتعلم Vue Router لأول مرة. أولها استخدام a بدلًا من RouterLink داخل التطبيق الداخلي، مما يؤدي إلى إعادة تحميل الصفحة أو سلوك غير متوقع. ثانيها نسيان <RouterView /> في المكان الصحيح. ثالثها الاعتماد على path دائمًا بدلًا من name، ثم معاناة لاحقة عند تغيير هيكل المسارات. رابعها عدم الانتباه إلى params وquery والخلط بينهما. خامسها وضع منطق حماية صفحات حساسة داخل الواجهة بدلًا من التعامل معه في guards أو على مستوى النظام بشكل منظم.
من الأخطاء أيضًا أن يحاول المطور حل كل شيء عبر router فقط. بينما الواقع أن الراوتر جزء من المنظومة، لا المنظومة كاملة. أحيانًا تحتاج إلى Pinia، وأحيانًا إلى composables، وأحيانًا إلى API service، وأحيانًا إلى lazy loading، وكل جزء له مكانه. عندما تعرف حدوده، يصبح استخدامه أكثر نجاحًا.
هل Vue Router مناسب للمشاريع الصغيرة أيضًا؟
نعم، وبقوة. حتى لو كان مشروعك صغيرًا اليوم، فإن استخدام Vue Router بشكل صحيح من البداية يمنحك بنية قابلة للتوسع لاحقًا. قد تبدأ بصفحتين أو ثلاث، ثم يتحول المشروع إلى متجر، أو لوحة بيانات، أو تطبيق إداري، أو بوابة محتوى. عندما يكون الراوتر منظمًا منذ البداية، لن تضطر إلى إعادة كتابة نصف المشروع لاحقًا.
والأجمل من ذلك أن Vue Router لا يفرض عليك تعقيدًا غير ضروري. يمكنك البدء ببنية بسيطة جدًا، ثم تضيف فقط ما تحتاجه: مسارات، روابط، حراسة، تنقل برمجي، مسارات متداخلة، وتحسينات الأداء. هذه المرونة هي أحد الأسباب التي جعلته جزءًا أساسيًا من عالم Vue.
خاتمة
استخدام Vue Router للتنقل بين الصفحات ليس مجرد إعداد تقني إضافي، بل هو قرار معماري صغير يصنع فرقًا كبيرًا في شكل التطبيق وسهولة استخدامه وصيانته. عندما تفهم كيف تعمل المسارات، وكيف يرتبط كل مسار بمكوّن، وكيف تستخدم RouterLink وRouterView وuseRouter وuseRoute، وكيف تنظّم params وquery، وكيف تضيف الحماية والتحميل الكسول و404 والـ nested routes، فأنت لا تتعلم أداة فقط، بل تبني طريقة تفكير أفضل في تصميم تطبيقات Vue.
والأهم من كل ذلك أن Vue Router يمنحك شعورًا بالترتيب. كل شيء له مكانه: المسار هنا، الصفحة هناك، التنقل في الوسط، والحماية قبل الدخول، والعرض في النهاية. هذا الترتيب هو ما يجعل تجربة المستخدم هادئة وواضحة وسلسة. وعندما يعمل التطبيق بهذه السلاسة، يشعر المستخدم أن كل شيء “طبيعي”، بينما أنت كمطور تعرف أن وراء هذا الشعور نظامًا ذكيًا ومحكمًا من التوجيه والتنقل.