Vue
03 / 08

Vue Router & Navigation

Vue Router & Navigation

Vue Router is the official routing library for Vue.js. It deeply integrates with Vue core to make building Single Page Applications with Vue a breeze.

Router Setup

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    // Lazy-loaded route
    component: () => import('../views/About.vue'),
  },
  {
    path: '/users/:id',
    name: 'UserProfile',
    component: () => import('../views/UserProfile.vue'),
    props: true, // Pass route params as props
  },
  {
    path: '/dashboard',
    component: () => import('../views/Dashboard.vue'),
    children: [
      {
        path: '',
        name: 'DashboardHome',
        component: () => import('../views/DashboardHome.vue'),
      },
      {
        path: 'settings',
        name: 'Settings',
        component: () => import('../views/Settings.vue'),
      },
    ],
  },
  {
    // 404 catch-all
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('../views/NotFound.vue'),
  },
]

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    }
    return { top: 0 }
  },
})

export default router

Navigation in Components

<script setup>
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

// Access route params
const userId = route.params.id
const query = route.query.search

// Programmatic navigation
const goToUser = (id) => {
  router.push(`/users/${id}`)
}

const goBack = () => {
  router.back()
}

const navigateWithQuery = () => {
  router.push({
    name: 'UserProfile',
    params: { id: 123 },
    query: { tab: 'posts' },
  })
}

// Replace instead of push (no history entry)
const replaceRoute = () => {
  router.replace('/new-location')
}
</script>

<template>
  <div>
    <!-- Declarative navigation -->
    <router-link to="/">Home</router-link>
    <router-link :to="{ name: 'About' }">About</router-link>
    <router-link :to="`/users/${userId}`">User Profile</router-link>
    
    <!-- Active link styling -->
    <router-link
      to="/dashboard"
      active-class="active"
      exact-active-class="exact-active"
    >
      Dashboard
    </router-link>
    
    <!-- Router view -->
    <router-view v-slot="{ Component }">
      <transition name="fade" mode="out-in">
        <component :is="Component" />
      </transition>
    </router-view>
  </div>
</template>

Navigation Guards

Navigation guards allow you to protect routes, redirect users, or perform actions before/after navigation.

// Global before guard
router.beforeEach((to, from, next) => {
  // Check authentication
  if (to.meta.requiresAuth && !isAuthenticated()) {
    next('/login')
  } else {
    next()
  }
})

// Global after guard
router.afterEach((to, from) => {
  // Analytics tracking
  trackPageView(to.fullPath)
})

// Per-route guard
const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from) => {
      if (!isAdmin()) {
        return '/'
      }
    },
  },
]

// In-component guards
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

// Prevent leaving with unsaved changes
const hasUnsavedChanges = ref(false)

onBeforeRouteLeave((to, from) => {
  if (hasUnsavedChanges.value) {
    const answer = window.confirm('You have unsaved changes. Leave anyway?')
    if (!answer) return false
  }
})

// React to param changes in same route
onBeforeRouteUpdate((to, from) => {
  // Fetch new user data when userId param changes
  if (to.params.id !== from.params.id) {
    fetchUser(to.params.id)
  }
})
</script>

Dynamic Routes & Nested Routes

const routes = [
  // Dynamic segments
  {
    path: '/users/:id',
    component: User,
  },
  {
    path: '/posts/:id(\\d+)', // Regex: only numbers
    component: Post,
  },
  {
    // Optional params
    path: '/search/:query?',
    component: Search,
  },
  {
    // Multiple params
    path: '/articles/:category/:slug',
    component: Article,
  },
  {
    // Catch-all route
    path: '/:pathMatch(.*)*',
    component: NotFound,
  },
  {
    // Nested routes
    path: '/dashboard',
    component: Dashboard,
    children: [
      {
        path: '', // Default child route
        component: DashboardHome,
      },
      {
        path: 'profile',
        component: Profile,
      },
      {
        path: 'settings',
        component: Settings,
        children: [
          {
            path: 'account',
            component: AccountSettings,
          },
          {
            path: 'privacy',
            component: PrivacySettings,
          },
        ],
      },
    ],
  },
]

Route Meta Fields & Lazy Loading

const routes = [
  {
    path: '/admin',
    component: Admin,
    meta: {
      requiresAuth: true,
      role: 'admin',
      title: 'Admin Panel',
    },
  },
  {
    path: '/public',
    component: Public,
    meta: {
      requiresAuth: false,
      layout: 'public',
    },
  },
]

// Access meta in components
<script setup>
import { useRoute } from 'vue-router'

const route = useRoute()

// Update page title based on meta
watch(
  () => route.meta.title,
  (title) => {
    document.title = title || 'My App'
  },
  { immediate: true }
)
</script>

// Lazy loading with named chunks
const routes = [
  {
    path: '/heavy',
    component: () => import(/* webpackChunkName: "heavy" */ '../views/Heavy.vue'),
  },
]

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free