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()
})
}