React

Vue Router navigation guards: order and return values

In Vue Router 4 a guard returns a value: false blocks, a route redirects, nothing continues. The order decides which one wins.

Key points

  • A guard in Vue Router 4 returns a value; next() is optional.
  • The order is beforeEach, beforeEnter, beforeRouteEnter, beforeResolve, afterEach.
  • Guards protect navigation, not data: a server-side check stays necessary.

A navigation guard in Vue Router 4 returns a value: false cancels the navigation, a route redirects it, no value lets it through. The next() style still works, but mixing both in one file is the most common cause of infinite redirects.

Order of execution

PhaseWhere you declare itWhat it can do
beforeEachrouter.beforeEachBlock or redirect every navigation
beforeEnterOn the routeApplies to that route only
beforeRouteEnterIn the componentEnter without access to this
beforeResolverouter.beforeResolveLast check before confirmation
afterEachrouter.afterEachEffects only: analytics, title

The order is the one in the table. Knowing it avoids the classic case: the same authentication check written in beforeEach and in beforeEnter, running twice for no reason.

Authentication without contradictions

router.beforeEach(async (to) => {
  const session = await getSession();
  if (!session && to.meta.requiresAuth) {
    return { name: "login", query: { next: to.fullPath } };
  }
  if (session && to.name === "login") {
    return { name: "dashboard" };
  }
});

The second condition is the one almost always missing. Without it a signed-in reader can open the login page: that is not a loop, it is a contradiction that confuses whoever is using the app.

Blocking the exit from a form

import { onBeforeRouteLeave } from "vue-router";

const dirty = ref(false);
onBeforeRouteLeave(() => {
  if (!dirty.value) return true;
  return window.confirm("Uscire senza salvare?");
});

onBeforeRouteLeave applies only to the component that declares it. Returning false cancels the navigation and the URL does not change.

Mistakes you see in production

  • A guard registered inside setup(): a new listener is added on every mount.
  • next() called twice in the same guard: the second call is ignored with a warning.
  • A redirect to the protected route without checking the session: loop.
  • An async guard without await on the session: the decision arrives after the navigation.

The known limitation

Guards protect navigation, not data. A client can call the API directly, so every protected route also needs a check on the server. The guard is for the interface, not for security.

Commands

npm install vue-router@4
npx vue-tsc --noEmit

From the catalogue: shadcn-vue, vue-bits and the Vue logo block. The guides: UI libraries for Vue 3 and structuring a Nuxt 3 project; the comparison: Vue against React in 2026.

Frequently asked questions

Is next() still supported?

Yes, but pick one style. In an async guard return the value and do not call next.

Why does my guard not see the store on the first visit?

Because it runs before the app mounts. Wait for router.isReady() or initialise the store before mounting.

How do I make a permanent redirect?

With a rule in createRouter, not with a guard. A guard would run on every navigation and add pointless work.

Is beforeRouteEnter different from beforeEnter?

Yes. beforeEnter is a route property, beforeRouteEnter is a component hook and has no access to this.

Can I cancel the Back button?

Yes, by returning false from onBeforeRouteLeave on the active component. You cannot block the user from closing the tab.