recorded decision · public · no signup

accepted2026-06-23vuejs/rfcs

Summary

What was chosen

  • Navigation guards will be allowed to return a value or a Promise instead of explicitly calling `next()`.adr
  • Vue Router will interpret the returned value of a 2-argument navigation guard as if passed to `next()`.adr
  • Returning `true` or `undefined` from a navigation guard will validate the navigation.adr
  • Returning `false` from a navigation guard will cancel the current navigation.adr
  • Returning a location object from a navigation guard will redirect to that specified location.adr

What was ruled out· 1

  • Implementing this functionality solely with a function helper was rejected to shift the guard paradigm.adr

Consequences

  • This change to navigation guards is not a breaking change for existing Vue Router applications.adr
  • The new navigation guard syntax avoids forgetting to call `next()` and prevents multiple calls.adr
  • Using `return` in navigation guards is more idiomatic as it can only be called once.adr
  • Implementing this feature in Vue Router 3 might incur a higher development cost.adr

The recorded why

Summary

Allow navigation guards to return the value or a Promise of it instead of calling next:

// change
router.beforeEach((to, from, next) => {
  if (!isAuthenticated) next(false)
  else next()
})

// into
router.beforeEach(() => isAuthenticated)

Since we can check the amount or arguments the navigation guard has, we can automatically know if we should look at the returned value or not. This is not a breaking change.

Motivation

  • Avoid forgetting to call next
  • Avoid the problem of calling next multiple times (since we can only return once)
  • return is more idiomatic since it can only be called once
  • Avoid the need of 3 arguments in the function if they are not all used

Detailed design

If 2 arguments are passed to a navigation guard, Vue Router will look at the returned value as if it was passed to next.

§ Validate/cancel a navigation

To validate a navigation, you currently have to call next() or next(true). Instead, you can not return anything (same as explicitly returning undefined) or return true. To cancel the navigation you could call next(false), now you can explicitly return false:

router.beforeEach((to) => {
  if (to.meta.requiresAuth && !isAuthenticated) return false
})

// with async / await
router.beforeEach(async (to) => {
  return await canAccessPage(to)
})

§ Redirect to a different location

We can redirect to a location by returning the same kind of object passed to router.push:

router.beforeEach((to) => {
  if (to.meta.requiresAuth && !isAuthenticated)
    return {
      name: 'Login',
      query: {
        redirectTo: to.fullPath,
      },
    }
})

// with async / await
router.beforeEach(async (to) => {
  if (!(await canAccessPage(to))) {
    return {
      name: 'Login',
      query: {
        redirectTo: to.fullPath,
      },
    }
  }
})

§ Errors

Unexpected errors can still be thrown synchronously or asynchronously:

router.beforeEach((to) => {
  throw new Error()
})

// with async / await
router.beforeEach(async (to) => {
  throw new Error()
})

// with promises
router.beforeEach((to) => {
  return Promise.reject(new Error())
})

Drawbacks

  • Vue Router 3 might preset a higher implementation cost

Alternatives

  • This could be implemented with a function helper but the idea is to shift the way we write navigation guards.

What other designs have been considered? What is the impact of not doing this?

Adoption strategy

Source: active-rfcs/0037-router-return-guards.md