Written by Paul Akinyemi✏️
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.
What are navigation guards?
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:
- Ensuring users have to log in to access an app
- Implementing role-based access control
- Implementing feature flags
Creating navigation guards with Nuxt middleware
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.
Middleware syntax
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:
- The
to
object represents the location the user wants to navigate to - The
from
object represents the location the user was in before they made the request
Here are a couple of the route
objects’ properties:
-
**fullPath**
: A string that represents the whole location, including thesearch
andhash
. 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 thepath
You 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.
Writing anonymous middleware
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.
Writing named middleware with defineNuxtRouteMiddleware
Named 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
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.
Middleware ordering
On any given route, middleware executes in this order:
- Global middleware
- Middleware defined in the
middleware
array ofdefinePageMeta
, in the order of appearance
Global 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
.
Best practices when working with navigation guards
The following are some guidelines to keep in mind when writing navigation guards.
Navigation guards should be lean
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.
Navigation guards should be pure functions
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.
Be careful of infinite redirects
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.
Conclusion
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.
Get set up with LogRocket's modern error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side.
NPM:
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Script Tag:
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
3.(Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
Top comments (0)