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