Frontend Development 13 min read

Deep Dive into Vue Router 4 Navigation Guard Source Code

This article thoroughly explains the core mechanisms of Vue Router 4 navigation guards, covering global, per‑route, and component guards, their classification, execution flow, and the underlying source‑code implementations such as useCallbacks, guardToPromiseFn, and runGuardQueue.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Deep Dive into Vue Router 4 Navigation Guard Source Code

The article begins by introducing the Vue Router 4 navigation‑guard section of a series that explores the library’s source code, reminding readers of previous articles on router creation and matcher logic.

Official definition : Navigation guards protect route transitions by allowing developers to approve or cancel navigation globally, per‑route, or within components, commonly used to check authentication or permissions via router.beforeEach .

Learning outcomes : Readers will gain a complete understanding of the guard core code, master the guard design pattern, and learn the execution order of global and per‑route guards.

Guard classification : Vue Router 4 defines three main guard categories – global guards (registered on the router instance), per‑route guards (defined in route records as beforeEnter ), and component guards (defined inside Vue components, e.g., beforeRouteEnter , beforeRouteUpdate , beforeRouteLeave ).

Full navigation flow : The article outlines the step‑by‑step process triggered by the router’s history listener (e.g., router.push , router.replace , router.go ), followed by the execution of global beforeEach , component‑level beforeRouteUpdate , per‑route beforeEnter , component beforeCreate and beforeRouteEnter , the parsing guard beforeResolve , the final afterEach , DOM updates, and finally the component beforeRouteLeave before unmounting.

Global guard implementation :

const beforeGuards = useCallbacks
>()
const beforeResolveGuards = useCallbacks
>()
const afterGuards = useCallbacks
()
return {
  beforeEach: beforeGuards.add,
  beforeResolve: beforeResolveGuards.add,
  afterEach: afterGuards.add,
}

The useCallbacks helper creates a mutable list of callbacks that can be added, listed, or reset:

export function useCallbacks
() {
  let handlers: T[] = []
  function add(handler: T): () => void {
    handlers.push(handler)
    return () => {
      const i = handlers.indexOf(handler)
      if (i > -1) handlers.splice(i, 1)
    }
  }
  function reset() { handlers = [] }
  return { add, list: () => handlers, reset }
}

guardToPromiseFn wraps a guard callback into a Promise so that both synchronous and asynchronous guards can be processed uniformly. It handles the five possible next signatures (no argument, false , Error , route location, or a callback) and rejects the navigation when appropriate.

const next: NavigationGuardNext = (valid?) => {
  if (valid === false) { reject(createRouterError(ErrorTypes.NAVIGATION_ABORTED, { from, to })) }
  else if (valid instanceof Error) { reject(valid) }
  else if (isRouteLocation(valid)) { reject(createRouterError(ErrorTypes.NAVIGATION_GUARD_REDIRECT, { from: to, to: valid })) }
  else if (enterCallbackArray && record!.enterCallbacks[name!] === enterCallbackArray && typeof valid === 'function') {
    enterCallbackArray.push(valid)
  }
  else { resolve() }
}

The guard execution pipeline uses runGuardQueue to chain the promises sequentially:

function runGuardQueue(guards: Lazy
[]): Promise
{
  return guards.reduce((promise, guard) => promise.then(() => guard()), Promise.resolve())
}

During navigation, the router builds a list of guards (global beforeEach , per‑route beforeEnter , component guards, etc.), converts each to a promise via guardToPromiseFn , appends a cancellation check, and finally runs the queue with runGuardQueue . After the navigation is confirmed, afterEach hooks are invoked.

Per‑route guard ( beforeEnter ) is retrieved from the route record and processed in the same way as global guards, ensuring it only runs for newly activated routes.

for (const record of to.matched) {
  if (record.beforeEnter && !from.matched.includes(record)) {
    if (Array.isArray(record.beforeEnter)) {
      for (const beforeEnter of record.beforeEnter) guards.push(guardToPromiseFn(beforeEnter, to, from))
    } else {
      guards.push(guardToPromiseFn(record.beforeEnter, to, from))
    }
  }
}
guards.push(canceledNavigationCheck)
return runGuardQueue(guards)

The article concludes that understanding these mechanisms allows developers to correctly implement navigation guards, use the next function appropriately, and avoid common pitfalls such as missing next calls or returning values without invoking next . The next installment will cover component‑level guards.

frontendJavaScriptRoutingVue.jsSource CodeVue RouterNavigation Guard
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.