Issue with Vue router - Multiple calls to the "next" callback were detected within one navigation guard

I have incorporated Vue 3 with Vue router 4 and have implemented middleware functions that my routes must pass through. However, I am encountering an error in Vue that states:

The "next" callback was invoked multiple times in a single navigation guard while transitioning from "/" to "/protected". It should only be invoked once in each navigation guard. This issue will cause problems in a production environment.

Below is my routes file:

import auth from '@/middleware/auth'
import admin from '@/middleware/admin'
import initRoutesMiddleware from '@/routes/middleware'
import { createWebHistory, createRouter } from 'vue-router'

const routes = [
    {
        path: '/',
        name: 'dashboard',
        component: () => import('@/views/DashboardView.vue'),

        meta: {
            middleware: [ auth ],
        },
    },

    {
        path: '/protected',
        name: 'protected',
        component: () => import('@/views/ProtectedView.vue'),

        meta: {
            middleware: [ auth, admin ],
        },
    },

    {
        path: '/access-denied',
        name: 'accessDenied',
        component: () => import('@/views/error/AccessDeniedView.vue'),
    },
]

const router = createRouter({
    history: createWebHistory(),
    routes: routes,
})

router.beforeEach((_, __, next) => {
    setAppLoadingCursor(false)
    
    return next()
})

initRoutesMiddleware(router)

export default router

Here is my middlware/auth.ts:

import { ROUTE_LOGIN } from '@/config/routes'
import { LOCALSTORAGE_KEY_USER, LOCALSTORAGE_KEY_PATH_BEFORE_LOGIN } from '@/config/app'

export default function auth({ to, next }: any) {
    const user = !!localStorage.getItem(LOCALSTORAGE_KEY_USER)

    if (!user) {
        localStorage.setItem(LOCALSTORAGE_KEY_PATH_BEFORE_LOGIN, to.path)

        return next(ROUTE_LOGIN.path)
    }

    return next()
}

and here is middleware/admin.ts:

import AppUser from '@/types/appUser'
import { LOCALSTORAGE_KEY_USER } from '@/config/app'
import { ROUTE_ACCESS_DENIED } from '@/config/routes'

export default function admin({ next }: any) {
    const user = localStorage.getItem(LOCALSTORAGE_KEY_USER)
    
    if (!user) return next()

    const appUser: AppUser = JSON.parse(user)

    if (appUser.admin !== true) return next(ROUTE_ACCESS_DENIED.path)

    return next()
}

Here is routes/middlware.ts:

import { Router, RouteLocationNormalized, NavigationGuardNext } from 'vue-router'

interface RouteContext {
    from: RouteLocationNormalized
    next: NavigationGuardNext
    router: Router
    to: RouteLocationNormalized
}

function nextFactory(context: RouteContext, middlewares: Function[], index: number) {
    const subsequentMiddleware = middlewares[index]

    if (!subsequentMiddleware) return context.next

    return (...parameters: any[]) => {
        context.next(...(parameters as []))
        const nextMiddleware = nextFactory(context, middlewares, index + 1)
        subsequentMiddleware({ ...context, next: nextMiddleware })
    }
}

export default function initRoutesMiddleware(router: Router) {
    router.beforeEach((to, from, next) => {
        if (to.meta.middleware) {
            const middlewares: Function[] = Array.isArray(to.meta.middleware)
                ? to.meta.middleware
                : [to.meta.middleware]

            const context: RouteContext = { from, next, router, to }
            const nextMiddleware = nextFactory(context, middlewares, 1)

            return middlewares[0]({ ...context, next: nextMiddleware })
        }

        return next()
    })
}

Answer №1

Consider implementing the following method (refer below for more details):

routes/middleware.ts

import { Router, RouteLocationNormalized, NavigationGuard } from "vue-router";

type GuardFn = (
  to: RouteLocationNormalized,
  from: RouteLocationNormalized
) => ReturnType<NavigationGuard>;

export default function initiateRoutesMiddleware(router: Router) {
  router.beforeEach(async (to, from) => {
    if (to.meta.middleware) {
      const middlewares: GuardFn[] = Array.isArray(to.meta.middleware)
        ? to.meta.middleware
        : [to.meta.middleware];

      for (const middleware of middlewares) {
        const result = await middleware(to, from);
        if (result !== undefined && result !== true) return result;
      }
    }
  });
}

middleware/admin.ts

type AppUser = { admin: boolean };
const LOCALSTORAGE_KEY_USER = "";
const ROUTE_ACCESS_DENIED = { path: "" };

export default function admin() {
  const user = localStorage.getItem(LOCALSTORAGE_KEY_USER);

  if (!user) return;

  const appUser: AppUser = JSON.parse(user);

  if (appUser.admin !== true) return ROUTE_ACCESS_DENIED.path;
}

middleware/auth.ts

import { RouteLocationNormalized } from "vue-router";

const ROUTE_LOGIN = { path: "" };
const LOCALSTORAGE_KEY_USER = "";
const LOCALSTORAGE_KEY_PATH_BEFORE_LOGIN = "";

export default function authenticate(to: RouteLocationNormalized) {
  const user = !!localStorage.getItem(LOCALSTORAGE_KEY_USER);

  if (!user) {
    localStorage.setItem(LOCALSTORAGE_KEY_PATH_BEFORE_LOGIN, to.path);
    return ROUTE_LOGIN.path;
  }
}

The code appears to have multiple occurrences of next, which can lead to issues due to the complexity of the recursion involved.
This can result in conflicts where each middleware tries to redirect independently, creating unwanted conflicts.

I have made significant revisions to the code:

  • Streamlined the process to iterate through the middleware functions, redirecting or halting navigation on the first applicable guard.

  • Updated the code to utilize return-style navigation guards (avoid using next; refer to Vue Router Docs - Navigation Guards - Optional Third Argument Next).

  • Implemented handling for asynchronous guard functions.

  • Enhanced type enforcement where feasible.

Placeholder types and values have been utilized, considering the limitations in accessing the complete project scope.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Conceal descendant of list item and reveal upon clicking

In my responsive side menu, there is a submenu structured like this: .navbar ul li ul I would like the child menus to be hidden and only shown when the parent menu is clicked. Although I attempted to achieve this with the following code, it was unsucces ...

Configuring Axios header in Java backend based on the value stored in the express session

I am currently working on a frontend app using node.js express for server-side rendering. This app calls java backend endpoints and I use Axios to make the requests. A specific header named "agent-id" needs to be set in every request that is sent from expr ...

Ways to guarantee that the factory promise is fulfilled prior to the execution of the

So far, I have always found valuable help by studying existing answers on stackoverflow, but now I've hit a roadblock. I'm currently working on an app that utilizes a directive to generate calendar month boxes. <app2directive class="column_5 ...

methods for transferring JSON data from JavaScript to PHP

I am trying to figure out how to parse JSON data from JavaScript to PHP. Here is my JavaScript code: var Dataconvert; var asetid = new Array(); $("#simpanmodifikasi").click(function(){ var table = $('#tableasal tbody' ...

Grouping object properties in a new array using Java Script

I'm currently learning Java script and attempting to merge an Array of objects based on certain properties of those objects. For instance, I have the following array which consists of objects with properties a, b, c, pet, and age. I aim to create a n ...

Using AJAX to dynamically load content from a Wordpress website

Currently, I have been experimenting with an AJAX tutorial in an attempt to dynamically load my WordPress post content onto the homepage of my website without triggering a full page reload. However, for some reason, when clicking on the links, instead of ...

What causes the first button to be clicked and the form to be submitted when the enter key is pressed within a text

Start by opening the javascript console, then place your cursor in the text box and hit enter. What is the reason for the function "baz" being called? How can this behavior be prevented? function foo() { console.log('foo'); } function bar() ...

Utilizing JavaScript to pass parameters to an IFRAME within Dynamics CRM

While trying to access http://localhost:80/testsite in an IFRAME, everything seems to work perfectly. However, once I attempt to pass field values as parameters, nothing happens. Oddly enough, accessing the page directly from a browser with parameters work ...

Using Laravel's compact function within Vue components

I am wondering how to pass variables to a Vue component in Laravel? When using blade, we can pass variables like: $now = Carbon::now(); return view('xxxxxxxx', compact('now'); This allows us to use $now in the xxxxxxxx blade file. Bu ...

Access an HTML page programmatically using JavaScript

Make sure to have a confirmation window pop up before submitting the form, and after confirming submission (by clicking OK), redirect to a confirmation page. Here's an example code snippet: <button type="submit" value="Save" id="Save" onclick="cl ...

Determine the horizontal movement of x and z on a flat surface while accounting for

Using HammerJS, I am able to manipulate a 3D object within an augmented reality environment. Everything functions properly unless I physically move my phone (which serves as the camera)... const newTranslation = new THREE.Vector3(this._initTranslation.x ...

Converting values to hexadecimal in PHP inspired by Javascript's .toString(16)

Is there a way to achieve the same result as JavaScript's .toString(16) in PHP? var n = 200000002713419; console.log(n.toString(16)); When executed, this code returns b5e6211de74b. Is there any equivalent function in PHP to achieve the same output? ...

What is the best way to iterate through an array and dynamically output the values?

I am facing an issue with a function that retrieves values from an array, and I wish to dynamically return these values. const AvailableUserRoles = [ { label: 'Administrator', value: 1 }, { label: 'Count', value: ...

N8N: Deleting JSON Key from Output

Is it possible to remove the json key in my output file? I am unsure of the best approach to achieve this. I would like to return an array of data with all values, not just one value. If you want more information, here is a link to my N8N post: Manipulate ...

Implementing setTimeout with the copy button: A guide

How can I implement a setTimeout function in the copy button so that when a user clicks on it, the text will change to "copied" and then revert back to "copy" after 3-4 seconds? Please help me find a solution to this problem and also optimize the JavaScrip ...

Create PDF and Excel files using Javascript on the client side

Are there any client-side Javascript frameworks that are comparable to Jasper Report in Java? I need to be able to generate both PDF and Excel files on the client side (browser) without relying on server-side processing. While I've come across Ja ...

What is the general consensus on combining SWR with Redux - is it considered a best practice?

I am currently incorporating both SWR and Redux into my code. Here is how I'm using them together: const vehiclesStates = useSelector(({ vehiclesStates: state }) => state); // REDUX const response = useFetch("/vehicles/states") // SWR con ...

A guide on testing mouse clientY in React using JEST for effective testing

useEffect(() => { const mouseHandler = (event: MouseEvent) => { menuData.forEach((element) => { if (element.hasActiveDropdown && event.clientY > 50) { handleCloseDropDown(); // handleDropDown('0') ...

Route Angular 4 application static resources to a /PORT directory rather than the primary domain

Our Angular 4 application is integrated within a Node + Express app and started like a typical node app, such as: node index.js On a local machine, the Angular app is served from the /client directory: app.use(express.static(__dirname + "/client")); Onc ...

Is there a way to hide the <v-otp-input> field in a Vue.js application?

For more information, please visit https://www.npmjs.com/package/@bachdgvn/vue-otp-input <script> export default { name: 'App', methods: { handleOnComplete(value) { console.log('OTP completed: ', value); } ...