framework
framework copied to clipboard
inconsistent behavior of route middlewares
Environment
- Operating System:
Darwin
- Node Version:
v16.13.2
- Nuxt Version:
3.0.0-rc.6
- Package Manager:
[email protected]
- Builder:
vite
- User Config:
-
- Runtime Modules:
-
- Build Modules:
-
Reproduction
this is module, please run yarn dev:prepare
before yarn dev
https://codesandbox.io/s/ancient-sea-noh9rh?file=/README.md
https://stackblitz.com/edit/node-89wpqd?file=playground%2Fnuxt.config.ts
https://github.com/mercs600/nuxt3-middleware-issue.git
Describe the bug
✋ I have noticed weird behavior when I work with route middlewares attached by module. I reproduced repository when you can check route middleware attached by file in middleware directory and also the same middleware but attached by plugin.
https://github.com/mercs600/nuxt3-middleware-issue/blob/main/playground/middleware/example.global.ts
https://github.com/mercs600/nuxt3-middleware-issue/blob/main/src/runtime/plugin.ts
These middlewares do the same work - resolve async function and set some state.
When I use middleware from plugin I get 500 error: nuxt instance unavailable
When I use middleware from playground/middleware
directory it works.
Should we expect some different behavior between middlewares from application and attach by modules ?
In the reproduce app you can enable middleware in plugin by nuxt.config (by default it use middleware from directory)
export default defineNuxtConfig({
modules: [MyModule],
myModule: {
addPlugin: true,
},
});
Additional context
No response
Logs
[h3] [unhandled] H3Error: nuxt instance unavailable
at createError (file:///Users/merc/Projects/Poligon/nuxt-module-middlwares/modules/mymodule/node_modules/h3/dist/index.mjs:196:15)
at Server.nodeHandler (file:///Users/merc/Projects/Poligon/nuxt-module-middlwares/modules/mymodule/node_modules/h3/dist/index.mjs:386:21)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
statusCode: 500,
fatal: false,
unhandled: true,
statusMessage: 'Internal Server Error'
}
[nuxt] [request error] nuxt instance unavailable
at createError (./node_modules/h3/dist/index.mjs:196:15)
at Server.nodeHandler (./node_modules/h3/dist/index.mjs:386:21)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
[Vue Router warn]: uncaught error during route navigation:
Error: nuxt instance unavailable
at Module.useNuxtApp (file:///Users/merc/Projects/Poligon/nuxt-module-middlwares/modules/mymodule/playground/.nuxt/dist/server/server.mjs:411:13)
at Module.useState (file:///Users/merc/Projects/Poligon/nuxt-module-middlwares/modules/mymodule/playground/.nuxt/dist/server/server.mjs:837:38)
at Module.useExampleState2 (file:///Users/merc/Projects/Poligon/nuxt-module-middlwares/modules/mymodule/playground/.nuxt/dist/server/server.mjs:2923:46)
at __vite_ssr_import_0__.addRouteMiddleware.global (file:///Users/merc/Projects/Poligon/nuxt-module-middlwares/modules/mymodule/playground/.nuxt/dist/server/server.mjs:2960:33)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Object.callAsync (file:///Users/merc/Projects/Poligon/nuxt-module-middlwares/modules/mymodule/node_modules/unctx/dist/index.mjs:45:16)
at async file:///Users/merc/Projects/Poligon/nuxt-module-middlwares/modules/mymodule/playground/.nuxt/dist/server/server.mjs:2533:22
We do a transform for middleware files to preserve the nuxt state across async operations, but in your case, adding an inline function from a plugin, we can't. So you can try something like this:
addRouteMiddleware('global-test', async (to) => {
await Promise.all([useExampleState(), useExampleState2()])
}, { global: true })
Or otherwise rewrite the code so that you don't chain awaits that rely on the nuxt context.
Another potential solution would be to deliberately provide that state yourself:
addRouteMiddleware('global-test', async (to) => {
await callWithNuxt(nuxtApp, useExampleState)
await callWithNuxt(nuxtApp, useExampleState2)
}, { global: true })
Alternatively, you can also define your middleware with defineNuxtRouteMiddleware
- we should be able to pick it up then also.
addRouteMiddleware('global-test', defineRouteMiddleware(async (to) => {
await useExampleState()
await useExampleState2()
}), { global: true })
Thank you @danielroe for your explanation.
It works except last solution when I wrapped with defineNuxtRouteMiddleware
.
I have resolved it with callWithNuxt
.
I will create PR to update documentation
I am running into a similar issue, trying to build a navigation guard.
If navigateTo is called after awaiting a promise (in my case importing jsonwebtoken on the server and verifying a token) the nuxt instance is not available in the catch block.
I tried using defineNuxtRouteMiddleware
as it seems to make the most sense in my case, but that does not help in my case.
import { PROTECTED_ROUTES } from '../config'
export default defineNuxtPlugin(() => {
addRouteMiddleware('auth', async (to) => {
const navigateToLogin = (to) => {
try {
// console.log('auth navigateToLogin', { server: process.server })
// const app = useNuxtApp()
// console.log('auth navigateToLogin nuxtapp:', { app })
return navigateTo({ path: '/login', query: { redirect: to.fullPath } })
} catch (error) {
// nuxt instance unavailable if called after await
console.log('auth navigateToLogin', { error })
}
}
const verifyToken = async (token) => {
const config = useRuntimeConfig()
const jwt = await import('jsonwebtoken').then(mod => mod.default)
return jwt.verify(token.value, config.public.tokenKey, {
algorithms: ['RS256'],
issuer: 'roller'
})
}
const handleClient = (to, token) => {
if (to.path === '/login') {
return navigateTo('/')
}
if (!token?.value) {
throw new Error('handleClient: Missing token')
}
}
if (!PROTECTED_ROUTES.includes(to.path)) {
return
}
const token = useCookie('JWT')
try {
if (!token.value) {
throw new Error('auth middleware: Missing Token')
}
if (process.server) {
await verifyToken(token)
} else {
return handleClient(to, token)
}
} catch (error) {
console.log('auth middleware: ', { error })
token.value = ''
return navigateToLogin(to)
}
}, {
global: true
})
})
UPDATE:
okay so after using callWithNuxt on the wrong function initally I got it to work now:
...
} catch (error) {
console.log('auth middleware: ', { error })
token.value = ''
return callWithNuxt(nuxtApp, () => navigateToLogin(to))
}
}, {
global: true
})
defineNuxtRouteMiddleware
did not work for me as well.
callWithNuxt
works for me theoretically, but the typescript linter complains about not knowing it (a missing auto import?). When I import callWithNuxt
from #app
it does not give a linter warning, but the build fails ("already imported"). So now I don't know what to do :3