Navigation guards allow you to control who can access specific parts of your application, ensuring only authorized users reach certain pages. This enhances security and improves user experience by keeping your app safe and efficient.
In this article, we’ll cover what navigation guards are, how they’re related to middleware, and how to write navigation guards in Nuxt 3. The term “middleware” will be used to refer to route middleware, which runs in the Vue part of your Nuxt apps and is a different beast from Nuxt’s server middleware.
Navigation guards are pieces of code that run after a page is requested but before it’s rendered, to make sure only authorized users access certain functionality. Navigation guards are used to implement features like:
To create navigation guards, we’ll be taking advantage of a concept in Nuxt called middleware. Middleware in Nuxt are special functions that run before your page is rendered, making them ideal for implementing navigation guards.
There are three types of middleware in Nuxt: anonymous, named, and global. When you want to use anonymous and named middleware, you must explicitly define it on the page you want to guard. In contrast, global middleware applies to all pages.
This is the general form all middleware takes:
(from, to) => {
if (someCondition) {
return navigateTo("/route")
}
if (otherCondition) {
return abortNavigation()
//or
return abortNavigation(error)
}
// return nothing to allow the request to proceed normally
}
Note that middleware can also be async functions, which allow you to await asynchronous requests if you need to.
Every middleware is a function that takes two route objects as arguments:
to object represents the location the user wants to navigate tofrom object represents the location the user was in before they made the requestHere are a couple of the route objects’ properties:
fullPath: A string that represents the whole location, including the search and hash. This string is percentage encoded (percentage encoding is the transformation applied to URLs when you enter them in a browser)params: An object of URL parameters extracted from the pathYou can find a full list of properties here.
The functions navigateTo and abortNavigation are what allow you to implement the functionality of navigation guards. navigateTo lets you redirect a user to another path, and abortNavigation allows you to kill the navigation entirely. Both functions are globally available and thus don’t need to be imported.
Now that we know how to write a middleware function, let’s see how to register one, either for a specific page or globally.
Anonymous middleware, also called inline middleware, is middleware that’s defined inside a page, and thus only ever runs on that page.
To write an anonymous middleware, all you have to do is write the middleware function inside the middleware array of the definePageMeta compiler macro.
Here’s an example:
// pages/staff.vue
<script setup>
definePageMeta({
middleware: [
function (to, from) {
// isUserAdmin() is a dummy function
const isAdmin = isUserAdmin()
if (!isAdmin) {
// Redirect the user to the homepage
return navigateTo('/')
}
// Else, proceed to the staff page
}
]
})
</script>
Note: you can define multiple anonymous middleware on one page, and they’ll be executed in the order they appear in the middleware array.
And that’s it! Next, we’ll look into writing named middleware.
defineNuxtRouteMiddlewareNamed middleware is middleware that’s defined in a standalone file in the middleware directory, and can be run on multiple pages. The name of the middleware is the kebab-case name of its containing file, so the middleware that lives in the file middleware/isAdmin.js will get the name is-admin.
To create named middleware, we need one more thing: a helper function called defineNuxtRouteMiddleware. This helper function takes a middleware function as its only argument.
Putting it all together to create a named middleware, you first have to create a file in the middleware directory, then write your middleware, pass it to defineNuxtRouteMiddleware, and export the returned value. Here’s an example:
// middleware/isAdmin.js
export default defineNuxtRouteMiddleware((to, from) => {
// isUserAdmin() is a dummy function
const isAdmin = isUserAdmin()
if (!isAdmin) {
// Redirect the user to the homepage
return navigateTo('/')
}
// Else, continue with navigation
})
Now that you’ve created the is-admin middleware, you can use it on any page by putting its name in the middleware array of the definePageMeta macro. Here’s an example:
// pages/staff.vue
<script setup>
definePageMeta({
middleware: ['is-admin']
})
</script>
And that’s it!
Writing global middleware works the same way as writing named middleware, with one difference: the name of the middleware file has to be suffixed with .global. So, the file will be named middleware/isAdmin.global.js instead of middleware/isAdmin.js, and then it’ll run on every route, without needing to be specified in definePageMeta.
Note that it’s not possible to define exceptions for global middleware — they always run on every route. If you want the global middleware to skip over a certain route, you have to write the check inside the middleware itself.
On any given route, middleware executes in this order:
middleware array of definePageMeta, in the order of appearanceGlobal middleware executes in alphabetical order based on the filename, but if you need to run middleware in a specific order, you can prefix it with a number, like so: middleware/01.isAdmin.global.js.
The following are some guidelines to keep in mind when writing navigation guards.
You should write navigation guards to do as little work as possible; the longer each function takes to run, the longer the user has to wait for the page to be rendered.
You should avoid making network requests if possible, and if you can’t, you should fetch and cache as much data at once as you can.
Even though Nuxt guarantees the execution order of your middleware, each middleware function should be free of side effects and should run exactly the same regardless of where in the middleware chain it executes.
If you’re not careful, it’s easy to create infinite redirects by writing middleware like this:
export default defineNuxtRouteMiddleware((to, from) => {
const { isAuthenticated } = dummyAuthFunction();
if (!isAuthenticated) {
return navigateTo('/login')
} else {
return navigateTo('/')
}
})
This middleware looks fine at first glance, but it has a problem: you’re always redirecting, regardless of the value of the condition. This will create an infinite loop if this middleware will also be executed when you visit the pages you’re redirecting to. Here’s how you can modify the code to fix this:
export default defineNuxtRouteMiddleware((to, from) => {
const { isAuthenticated } = dummyAuthFunction();
if (!isAuthenticated) {
return navigateTo('/login')
}
})
The key here is, instead of redirecting to a specific route, return nothing (which allows the navigation to continue) when you don’t need to deflect the user.
Navigation guards are powerful features in Nuxt 3 that help you control access to your application’s routes. By leveraging middleware — whether anonymous, named, or global — you can efficiently implement these guards to enhance security and user experience. Keep your middleware lean and pure, and you’ll ensure smooth and effective navigation throughout your app.

Local AI proxy tutorial for detecting, masking, and rehydrating PII before prompts reach cloud LLMs.

Learn how Graph RAG uses connected knowledge structures to improve retrieval beyond simple text similarity.

AI can make PM thinking generic. Learn how to use it without losing judgment, user nuance, or product differentiation.

Learn how sibling-index() enables clean, JavaScript-free stagger animations using native CSS.