New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
using try/catch with an async method inside a middleware causes a nuxt instance unavailable
error.
#14269
Comments
hi @amrnn90! Did you find workaround? |
Hi @KonstantinVlasov, Ideally the usage of |
There is also a simple repro here: https://stackblitz.com/edit/nuxt-starter-d61kia?file=pages%2Findex.vue <script setup lang="ts">
definePageMeta({
middleware: async () => {
try {
// await new Promise((resolve) => setTimeout(resolve, 200)); // having await in both middleware and setup throw
throw new Error('Nope');
} catch (error) {
return navigateTo('/auth');
}
},
});
const { data } = await useFetch('/api'); // this throw "nuxt instance unavailable" during initial view
// const { data } = useLazyFetch('/api'); // this works
</script>
<template>
<div>
<h1>index</h1>
<pre>{{ data }}</pre>
</div>
</template>
|
Hi! Sorry this issue was left unanswered for so long. Short story: Calling Here is a quick solution: (Update: upstream issue fixed. You probably don't need this workaround anymore! Just update the lockfile.)
import { callWithNuxt } from '#app'
export default defineNuxtRouteMiddleware(async (to, from) => {
const nuxtApp = useNuxtApp()
try {
user = await fetchUser()
} catch (e) {
user = null
}
if (!user) {
return callWithNuxt(nuxtApp, navigateTo, ['/auth'])
}
}) Updated sandbox: https://stackblitz.com/edit/nuxt-starter-u5esgu?file=middleware%2Fauth.ts,app.vue And the story...Let me explain what happens when using The way Vue.js composition API (and Nuxt composables similarly) work is depending on an implicit context. During the lifecycle, vue sets the temporary instance of the current component (and nuxt temporary instance of nuxtApp) to a global variable and unsets it in same tick. When rendering on the server side, there are multiple requests from different users and nuxtApp running in a same global context. Because of this, nuxt and vue immediately unset this global instance to avoid leaking a shared reference between two users or components. What it does means? Composition API and Nuxt Composables are only available during lifecycle and in same tick before any async operation: // --- vue internal ---
const _vueInstance = null
const getCurrentInstance = () => _vueInstance
// ---
// Vue / Nuxt sets a global variable referencing to current component in _vueInstance when calling setup()
async setup() {
getCurrentInstance() // Works
await someAsyncOperation() // Vue unsets the context in same tick before async operation!
getCurrentInstance() // null
} The classic solution to this, is caching the current instance on first call to a local variable like To overcome this limitation, Vue does some dark magic when compiling our application code and restores context after each call for const __instance = getCurrentInstance() // Generated by vue compiler
getCurrentInstance() // Works!
await someAsyncOperation() // Vue unsets the context
__restoreInstance(__instance) // Generated by vue compiler
getCurrentInstance() // Still works! For a better description of what Vue actually does, see unjs/unctx#2 (comment). In Nuxt, we have an (internal) utility Nuxt 3 internally use unjs/unctx to support composables similar to vue for plugins and middleware. This enabled us to make composables like With Nuxt composables, we have the same design of Vue Composition API therefore needed a similar solution to magically do this transform. Check out unjs/unctx#2 (Proposal), unjs/unctx#4 (Transform implementation), and nuxt/framework#3884 (Integration to Nuxt). Vue currently only supports async context restoration for The..Bug..: The unctx transformation to automatically restore context seems buggy with |
Hey Pooya! Thanks for the complete answer <3 |
Docs ~> #14723 Your welcome @stafyniaksacha :) I'm sure you got it from the explanation above but for reference, also inline middleware needs the wrapper to leverage the auto transform (and typing!) definePageMeta({
middleware: defineNuxtRouteMiddleware(async () => {
await new Promise((resolve) => setTimeout(resolve, 200)); // having await in
return navigateTo('/auth');
}),
}) |
Upstream fix: unjs/unctx#28 |
Issue should be solved now! Make sure to update the lockfile for fix. https://stackblitz.com/edit/nuxt-starter-u5esgu?file=middleware%2Fauth.ts,app.vue |
… add docs security section, add docs custom sign in page section (#31) * feat: make useSession isomorph :confetti: * feat(docs): more realistic credentials flow * polish: security section in docs, rename some useSession internals for more consistency * docs: add isomorphic auth to docs * Revert "docs: add isomorphic auth to docs" This reverts commit 4cfaa3e. * docs: add isomorphic auth to docs * feat: switch to approach that avoids double-async nested composables (see https://github.com/nuxt/framework/issues/5740\#issuecomment-1229197529) * polish: remove bad redirect fallback, fix typing * polish: use FetchOptions directly, add ofetch as dev-dep * docs: add section on custom signin page
Hi, I am having the same issue but with a request from a created method. Basically I am trying to do a simple fetch outside the setup function. async created() { In the fetch method I'm trying to call nuxt auth composable to retrieve the status of the user. const { status, data } = useAuth(); The error is from useAuth(); I tried using callWithNuxt(who by the way is nowhere mentioned in the docs) and it doesn't work. If I do a entire refactoring and put the call in the setup script or method it works and I understand that I have an option to fix it but the most common use case is to do fetch from create or any other functions. I don't understand how an issue with some many complaints it's closed. Not every component can be written with the setup method in the best way and the main thing of Vue is that it let's you use both composition and options api. Here is still has an advantage over react. Could you give us another solution to avoid this error? Thanks. |
I ran into this issue too(it was way too dank to debug and fix btw). I feel like I understand better now thanks to an expanded explanation in the #14269 (comment). I was able to fix my stuff by following how it's done in the next-auth: https://github.com/sidebase/nuxt-auth/blob/main/src/runtime/composables/authjs/useAuth.ts#L60. I have a custom page middleware with some async stuff going on and which had a weird behavior, but after wrapping the middleware function with Fix that worked for me:
Example: // utils/data.ts
export const moreCoolStuff = async (nuxt: NuxtApp) => {
const result = await callWithNuxt(nuxt, () => useFetch('/api/cool-stuff'))
// ...
}
// composables/cool-stuff.ts
export const useCoolStuff = async (nuxt: NuxtApp) => {
try {
await moreCoolStuff(nuxt)
} catch(error) {
return await callWithNuxt(nuxt, () => navigateTo('/failed'))
}
} <!-- page.vue -->
<script setup lang="ts">
definePageMeta({
middleware: [
defineNuxtRouteMiddleware(async (to, from) => {
const nuxt = useNuxtApp()
return await useCoolStuff(nuxt, to)
})
]
})
</script> |
Environment
Linux
v17.9.0
3.0.0-rc.4
npm@8.5.5
vite
modules
,runtimeConfig
,autoImports
@nuxtjs/tailwindcss@5.1.3
-
Reproduction
Visit the
/secret
page to see the error:https://stackblitz.com/edit/nuxt-starter-elpq43?file=middleware%2Fauth.ts
Describe the bug
I'm trying to write an async middleware. However, when catching the rejected promise an error is thrown by nuxt when using
navigateTo()
:Additional context
No response
Logs
No response
The text was updated successfully, but these errors were encountered: