framework icon indicating copy to clipboard operation
framework copied to clipboard

inconsistent behavior of route middlewares

Open mercs600 opened this issue 1 year ago • 2 comments

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

mercs600 avatar Aug 02 '22 07:08 mercs600

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

danielroe avatar Aug 02 '22 08:08 danielroe

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

mercs600 avatar Aug 02 '22 11:08 mercs600

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

qucode1 avatar Oct 20 '22 10:10 qucode1

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

dargmuesli avatar Nov 05 '22 20:11 dargmuesli