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
| Phase | Where you declare it | What it can do |
|---|---|---|
| beforeEach | router.beforeEach | Block or redirect every navigation |
| beforeEnter | On the route | Applies to that route only |
| beforeRouteEnter | In the component | Enter without access to this |
| beforeResolve | router.beforeResolve | Last check before confirmation |
| afterEach | router.afterEach | Effects 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
awaiton 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.