Vue Router
A comprehensive guide to Vue Router, the official router for Vue.js.
Introduction to Vue Router
Vue Router is the official routing library for Vue.js. It deeply integrates with Vue.js core to make building Single Page Applications with Vue.js a breeze. Features include:
- Nested route/view mapping
- Modular, component-based router configuration
- Route params, query, wildcards
- View transition effects powered by Vue.js transition system
- Fine-grained navigation control
- Links with automatic active CSS classes
- HTML5 history mode or hash mode
- Customizable scroll behavior
- Proper encoding for URLs
Installation
Direct Download / CDN
html
<script src="https://unpkg.com/vue-router@4"></script>
NPM
bash
npm install vue-router@4
Yarn
bash
yarn add vue-router@4
Basic Usage
Creating a Router Instance
javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
Integrating with Vue Application
javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
Using Router in Components
vue
<!-- App.vue -->
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view/>
</div>
</template>
Core Concepts
Dynamic Route Matching
javascript
const routes = [
// dynamic segments start with a colon
{ path: '/user/:id', component: User }
]
Accessing route parameters:
vue
<template>
<div>User {{ $route.params.id }}</div>
</template>
<!-- Or with composition API -->
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id)
</script>
Nested Routes
javascript
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
// UserProfile will be rendered inside User's <router-view>
// when /user/:id/profile is matched
path: 'profile',
component: UserProfile
},
{
// UserPosts will be rendered inside User's <router-view>
// when /user/:id/posts is matched
path: 'posts',
component: UserPosts
}
]
}
]
Programmatic Navigation
javascript
// In component methods
methods: {
goToHome() {
this.$router.push('/')
},
goToUser(userId) {
this.$router.push(`/user/${userId}`)
},
goBack() {
this.$router.go(-1)
}
}
// With composition API
import { useRouter } from 'vue-router'
const router = useRouter()
function goToHome() {
router.push('/')
}
Named Routes
javascript
const routes = [
{
path: '/user/:id',
name: 'user',
component: User
}
]
// Navigate to named route
router.push({ name: 'user', params: { id: '123' } })
Named Views
vue
<template>
<div>
<router-view name="header"></router-view>
<router-view></router-view>
<router-view name="footer"></router-view>
</div>
</template>
javascript
const routes = [
{
path: '/',
components: {
default: Home,
header: Header,
footer: Footer
}
}
]
Advanced Features
Navigation Guards
Global Guards
javascript
const router = createRouter({ ... })
// Global before guard
router.beforeEach((to, from) => {
// Check if the user is logged in
const isAuthenticated = checkAuth()
// If not authenticated and trying to access a restricted page
if (!isAuthenticated && to.meta.requiresAuth) {
// Redirect to login page
return { name: 'Login' }
}
})
// Global after hook
router.afterEach((to, from) => {
// Send analytics data
sendAnalytics(to.fullPath)
})
Per-Route Guards
javascript
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from) => {
// Check if user is admin
if (!isAdmin()) {
return false
}
}
}
]
Component Guards
vue
<script>
export default {
beforeRouteEnter(to, from, next) {
// Called before the component is created
// "this" is not available here
next(vm => {
// Access to component instance via `vm`
})
},
beforeRouteUpdate(to, from) {
// Called when the route changes but the component is reused
// "this" is available
},
beforeRouteLeave(to, from) {
// Called when navigating away from this component
// "this" is available
// Example: Prevent leaving if form is dirty
if (this.formIsDirty) {
if (!window.confirm('Discard changes?')) {
return false
}
}
}
}
</script>
Route Meta Fields
javascript
const routes = [
{
path: '/admin',
component: Admin,
meta: {
requiresAuth: true,
title: 'Admin Panel',
roles: ['admin', 'super-admin']
}
}
]
// Using meta fields
router.beforeEach((to, from) => {
// Set document title
document.title = to.meta.title || 'Default Title'
// Check permissions
if (to.meta.requiresAuth && !isAuthenticated()) {
return { name: 'Login' }
}
if (to.meta.roles && !to.meta.roles.includes(currentUserRole)) {
return { name: 'Forbidden' }
}
})
Lazy Loading Routes
javascript
// Instead of importing synchronously
// import UserDetails from './views/UserDetails.vue'
// Use dynamic import for lazy loading
const routes = [
{
path: '/user/:id',
name: 'UserDetails',
component: () => import('./views/UserDetails.vue')
}
]
Scroll Behavior
javascript
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// Return to saved position if exists
if (savedPosition) {
return savedPosition
}
// Scroll to top for new navigation
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth'
}
}
// Always scroll to top
return { top: 0 }
}
})
Best Practices
Route Organization
For large applications, it's better to organize routes into modules:
javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import homeRoutes from './modules/home'
import userRoutes from './modules/user'
import productRoutes from './modules/product'
const routes = [
...homeRoutes,
...userRoutes,
...productRoutes,
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
// router/modules/user.js
export default [
{
path: '/users',
component: () => import('@/views/users/UserList.vue'),
children: [
{
path: ':id',
component: () => import('@/views/users/UserDetails.vue')
}
]
}
]
Authentication and Authorization
javascript
// router/index.js
const router = createRouter({ ... })
router.beforeEach(async (to, from) => {
// Check if route requires authentication
if (to.matched.some(record => record.meta.requiresAuth)) {
// Check if user is logged in
const isLoggedIn = await checkUserSession()
if (!isLoggedIn) {
// Save the location we were trying to access
return {
name: 'Login',
query: { redirect: to.fullPath }
}
}
// Check for required permissions
if (to.meta.requiredPermissions) {
const userPermissions = await getUserPermissions()
// Check if user has all required permissions
const hasPermission = to.meta.requiredPermissions.every(
permission => userPermissions.includes(permission)
)
if (!hasPermission) {
return { name: 'Forbidden' }
}
}
}
})
// After login, redirect to saved location
function handleLoginSuccess() {
const redirectPath = router.currentRoute.value.query.redirect || '/'
router.push(redirectPath)
}
Route Transitions
vue
<!-- App.vue -->
<template>
<div id="app">
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
</div>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
Route-Based Code Splitting
javascript
const routes = [
{
path: '/dashboard',
name: 'Dashboard',
// Use webpackChunkName to group related chunks
component: () => import(/* webpackChunkName: "dashboard" */ './views/Dashboard.vue'),
children: [
{
path: 'analytics',
name: 'Analytics',
component: () => import(/* webpackChunkName: "dashboard" */ './views/Analytics.vue')
},
{
path: 'settings',
name: 'Settings',
component: () => import(/* webpackChunkName: "dashboard" */ './views/Settings.vue')
}
]
}
]
Working with Vue Router in Composition API
Using Router and Route
vue
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// Access route params
console.log(route.params.id)
// Navigate programmatically
function navigateTo(id) {
router.push({ name: 'UserDetails', params: { id } })
}
// Watch for route changes
import { watch } from 'vue'
watch(
() => route.params,
(newParams) => {
console.log('Route params changed:', newParams)
// Fetch new data based on params
}
)
</script>
Custom Router Composables
javascript
// composables/useAuth.js
import { computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
export function useAuth() {
const router = useRouter()
const route = useRoute()
const isAuthenticated = computed(() => {
return !!localStorage.getItem('token')
})
function login(credentials) {
// Login logic
return api.login(credentials).then(response => {
localStorage.setItem('token', response.token)
// Redirect to original destination or home
const redirectPath = route.query.redirect || '/'
router.push(redirectPath)
})
}
function logout() {
localStorage.removeItem('token')
router.push('/login')
}
return {
isAuthenticated,
login,
logout
}
}
// Usage in component
import { useAuth } from '@/composables/useAuth'
const { isAuthenticated, login, logout } = useAuth()
Testing Vue Router
Testing with Vue Test Utils
javascript
import { mount } from '@vue/test-utils'
import { createRouter, createWebHistory } from 'vue-router'
import App from '@/App.vue'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
// Create router instance for testing
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
const router = createRouter({
history: createWebHistory(),
routes
})
test('routing', async () => {
router.push('/')
await router.isReady()
const wrapper = mount(App, {
global: {
plugins: [router]
}
})
expect(wrapper.html()).toContain('Home page')
// Navigate to about page
await wrapper.find('a[href="/about"]').trigger('click')
// Wait for route change
await router.isReady()
// Check if about page is rendered
expect(wrapper.html()).toContain('About page')
})
Testing Navigation Guards
javascript
import { beforeEach } from '@/router'
// Mock dependencies
jest.mock('@/services/auth', () => ({
isAuthenticated: jest.fn()
}))
import { isAuthenticated } from '@/services/auth'
describe('Router Guards', () => {
test('redirects to login when accessing protected route unauthenticated', () => {
// Setup
isAuthenticated.mockReturnValue(false)
const to = {
matched: [{ meta: { requiresAuth: true } }],
fullPath: '/dashboard'
}
const from = {}
// Execute guard
const result = beforeEach(to, from)
// Assert
expect(result).toEqual({
name: 'Login',
query: { redirect: '/dashboard' }
})
})
test('allows access to protected route when authenticated', () => {
// Setup
isAuthenticated.mockReturnValue(true)
const to = {
matched: [{ meta: { requiresAuth: true } }]
}
const from = {}
// Execute guard
const result = beforeEach(to, from)
// Assert
expect(result).toBeUndefined()
})
})
Frequently Asked Questions
How do I handle 404 pages?
javascript
const routes = [
// Other routes...
// This will match any path that hasn't been matched by previous routes
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound
}
]
How do I handle route transitions?
Use Vue's transition component around your router-view:
vue
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
How do I pass props to route components?
javascript
const routes = [
{
path: '/user/:id',
component: User,
// Enable props for route
props: true
},
{
path: '/search',
component: SearchResults,
// Function mode allows transforming params
props: route => ({ query: route.query.q })
}
]
// In the component
export default {
props: ['id'] // or props: ['query']
}
How do I handle route-based permissions?
Use meta fields and navigation guards:
javascript
const routes = [
{
path: '/admin',
component: Admin,
meta: { requiredRole: 'admin' }
}
]
router.beforeEach((to, from) => {
if (to.meta.requiredRole && userRole !== to.meta.requiredRole) {
return { name: 'Forbidden' }
}
})
How do I handle query parameters?
javascript
// Navigate with query params
router.push({ path: '/search', query: { q: 'vue router' } })
// Access query params
const query = route.query.q
Related Resources
- Vue Router Official Documentation
- Vue.js Official Documentation
- Vue Router GitHub Repository
- Vue Router Examples
- More Tutorials
This document will be continuously updated. If you have any questions, please provide feedback through GitHub Issues.