DEV Community

Cover image for Navigation guards in Nuxt 3 with defineNuxtRouteMiddleware
Megan Lee for LogRocket

Posted on • Originally published at blog.logrocket.com

Navigation guards in Nuxt 3 with defineNuxtRouteMiddleware

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
}
Enter fullscreen mode Exit fullscreen mode

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 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 path

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>
Enter fullscreen mode Exit fullscreen mode

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
    })
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 of definePageMeta, 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('/')
    }
})
Enter fullscreen mode Exit fullscreen mode

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')
    }
})
Enter fullscreen mode Exit fullscreen mode

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:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. 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');
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

3.(Optional) Install plugins for deeper integrations with your stack:

  • Redux middleware
  • ngrx middleware
  • Vuex plugin

Get started now.

Top comments (0)