Skip to content

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 ​

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

This document will be continuously updated. If you have any questions, please provide feedback through GitHub Issues.

VitePress Development Guide