Module: Vue Routing

Navigation guards

Vue Routing: Navigation Guards

Navigation guards are functions that are executed before or after a route is navigated to. They provide a powerful way to control access to routes, perform actions before a route changes, or even redirect users. They're a core part of building secure and dynamic Vue applications with Vue Router.

Types of Navigation Guards

There are three main types of navigation guards:

  • Global Guards: Applied to all routes in your application.
  • Route Guards: Applied to specific routes.
  • Component Guards: Applied to a specific component rendered by a route.

1. Global Guards

Global guards are defined using router.beforeEach, router.beforeResolve, and router.afterEach on the router instance.

  • router.beforeEach( (to, from, next) => { ... }): This guard is executed before the route is navigated to. It's the most commonly used guard.

    • to: The target Route object being navigated to.
    • from: The current Route object being navigated away from.
    • next: A function that must be called to resolve the guard. It accepts three arguments:
      • next(): Proceed to the next route.
      • next(false): Abort the current navigation.
      • next('/path'): Redirect to a different route.
      • next({ path: '/path', replace: true }): Redirect and replace the current history entry.
  • router.beforeResolve( (to, from, next) => { ... }): Similar to beforeEach, but called after all component instances have been created and before the route is fully resolved. Useful for asynchronous operations that need to happen after components are mounted.

  • router.afterEach( (to, from) => { ... }): This guard is executed after the route has been navigated to and the component has been mounted. It doesn't receive the next function because the navigation has already completed. Useful for analytics, page title updates, or other post-navigation tasks.

Example (Global Guard - Authentication):

// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  {
    path: '/login',
    name: 'login',
    component: Login
  }
]

const router = new VueRouter({
  routes
})

router.beforeEach((to, from, next) => {
  const isLoggedIn = localStorage.getItem('isLoggedIn'); // Example authentication check

  if (to.name === 'login') {
    if (isLoggedIn) {
      next('/'); // Redirect to home if already logged in
    } else {
      next(); // Proceed to login page
    }
  } else {
    if (!isLoggedIn) {
      next('/login'); // Redirect to login if not logged in
    } else {
      next(); // Proceed to the route
    }
  }
})

export default router

2. Route Guards

Route guards are defined directly within the route configuration object using the beforeEnter property.

  • beforeEnter( (to, from, next) => { ... }): Functions the same as router.beforeEach, but applies only to the specific route.

Example (Route Guard - Role-Based Access):

// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Admin from '../views/Admin.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  {
    path: '/admin',
    name: 'admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
      const userRole = localStorage.getItem('userRole'); // Example role check

      if (userRole === 'admin') {
        next(); // Proceed to admin page
      } else {
        next('/'); // Redirect to home if not an admin
      }
    }
  }
]

const router = new VueRouter({
  routes
})

export default router

3. Component Guards

Component guards are defined within the component itself using beforeRouteEnter, beforeRouteUpdate, and beforeRouteLeave.

  • beforeRouteEnter(to, from, next) { ... }: Called before the component is created, allowing you to access the route being navigated to. You cannot access the component instance (this) within this guard. Use next(vm => { ... }) to access the component instance after it's created.

    • to: The target Route object being navigated to.
    • from: The current Route object being navigated away from.
    • next: A function that must be called to resolve the guard.
  • beforeRouteUpdate(to, from, next) { ... }: Called when the route associated with the component changes, but the component instance is reused. Useful for updating data based on route parameters. You can access the component instance (this) within this guard.

  • beforeRouteLeave(from, next) { ... }: Called when the component is about to be navigated away from. Useful for confirming changes or saving data. You can access the component instance (this) within this guard.

Example (Component Guard - Confirmation Before Leaving):

<template>
  <div>
    <h1>My Component</h1>
    <p>Unsaved changes will be lost!</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isDirty: true // Example: Component has unsaved changes
    };
  },
  beforeRouteLeave(to, from, next) {
    if (this.isDirty) {
      if (confirm('Are you sure you want to leave? Unsaved changes will be lost.')) {
        next();
      } else {
        next(false); // Cancel navigation
      }
    } else {
      next(); // Proceed to the next route
    }
  }
};
</script>

Guard Execution Order

When multiple guards are involved, they are executed in the following order:

  1. Global beforeEach guards
  2. Route-specific beforeEnter guards
  3. Component beforeRouteEnter guards (for the component being navigated to)
  4. Component beforeRouteUpdate guards (if the component is reused)
  5. Global beforeResolve guards
  6. Component beforeRouteLeave guards (for the component being navigated away from)
  7. Global afterEach guards

Important Considerations

  • next() is crucial: Always call next() (or next(false) or next('/path')) within each guard to resolve the navigation. Failing to do so will cause the navigation to hang.
  • Asynchronous Operations: Use async/await or Promises within guards to handle asynchronous operations (e.g., fetching data from an API). Make sure to call next() after the asynchronous operation completes.
  • Guard Complexity: Keep guards concise and focused on specific tasks. Complex logic should be moved to separate functions or services.
  • Avoid Side Effects in afterEach: afterEach should primarily be used for logging or analytics, as it's executed after the navigation has completed and cannot affect the outcome.

This provides a comprehensive overview of Vue Router navigation guards. Remember to choose the appropriate guard type based on your specific needs and to always call next() to resolve the navigation.