Skip to content
← All questions
⚡ Intermediate

How does Vue Router work and what are navigation guards?

🧭 Vue Router

Vue Router maps URL paths to components. When the URL changes, Vue Router renders the matching component without a full page reload. Navigation guards are hooks that run before, during, or after each navigation, letting you control access, cancel navigations, or run side effects.

Basic setup ​

ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: () => import('./views/Home.vue') },
    {
      path: '/dashboard',
      component: () => import('./views/Dashboard.vue'),
      meta: { requiresAuth: true }
    },
    {
      path: '/:pathMatch(.*)*',
      component: () => import('./views/NotFound.vue')
    }
  ]
})

createWebHistory() uses the browser's History API for clean URLs (/dashboard). createWebHashHistory() uses hash-based URLs (/#/dashboard) for environments without server-side URL rewriting.

Each () => import(...) is a dynamic import that creates a separate JavaScript chunk, loaded only when the user navigates to that route.

Guards intercept navigations at three levels: global, per-route, and in-component.

Global guards ​

Run on every navigation in the app:

ts
router.beforeEach((to, from) => {
  if (to.meta.requiresAuth && !isAuthenticated()) {
    return { path: '/login', query: { redirect: to.fullPath } }
  }
})

Returning a route object redirects. Returning false cancels the navigation. Returning nothing (or true) allows it.

Per-route guards ​

Run only for a specific route:

ts
const routes = [
  {
    path: '/admin',
    component: () => import('./views/Admin.vue'),
    beforeEnter: (to, from) => {
      if (!isAdmin()) return { path: '/' }
    }
  }
]

In-component guards ​

Run inside the component being navigated to or from:

vue
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

onBeforeRouteLeave((to, from) => {
  if (hasUnsavedChanges.value) {
    return confirm('Leave without saving?')
  }
})

onBeforeRouteUpdate((to, from) => {
  // same component, different params (e.g. /users/1 → /users/2)
  loadUser(to.params.id)
})
</script>
Open in Vue Playground

Guard execution order ​

When navigating from /a to /b:

Flowchart showing Vue Router navigation guard execution order from onBeforeRouteLeave to afterEach
  1. onBeforeRouteLeave in the component being left
  2. router.beforeEach (global)
  3. beforeEnter on the target route (per-route)
  4. onBeforeRouteUpdate if reusing a component
  5. router.beforeResolve (global, after async components resolve)
  6. Navigation confirmed
  7. router.afterEach (global, no cancellation possible)

Route meta ​

Attach arbitrary data to routes via meta. Guards and components can read it:

ts
const routes = [
  {
    path: '/settings',
    component: () => import('./views/Settings.vue'),
    meta: { requiresAuth: true, title: 'Settings' }
  }
]

router.afterEach((to) => {
  document.title = (to.meta.title as string) ?? 'My App'
})

Common patterns ​

NeedGuardWhere
Protect authenticated routesbeforeEachGlobal
Prevent leaving unsaved formsonBeforeRouteLeaveIn-component
Redirect old URLsbeforeEnterPer-route
Analytics/page trackingafterEachGlobal
Set page titleafterEachGlobal
Wait for async data before showing pagebeforeResolveGlobal

See also: How do you implement authentication with Vue Router? · How does Nuxt file-based routing work? · How would you implement lazy loading?

References ​

Released under the MIT License.