N
Nuxt2w ago
LL3006

callOnce loses Nuxt context after first await

I have a setup like this, where quote is a pinia store and it works
await callOnce(async () => {
await quote.loadQuote(); // This calls useNuxtApp() somewhere
});
await callOnce(async () => {
await quote.loadQuote(); // This calls useNuxtApp() somewhere
});
However as soon as I await for anything else inside callOnce, like e.g.
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

await callOnce(async () => {
await sleep(100);
await quote.loadQuote(); // This calls useNuxtApp() somewhere
});
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

await callOnce(async () => {
await sleep(100);
await quote.loadQuote(); // This calls useNuxtApp() somewhere
});
Suddenly, the page errors out with a
[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables. at useNuxtApp (/home/ll3006/sample-project/node_modules/nuxt/dist/app/nuxt.js:248:13) at useAPI (/home/ll3006/sample-project/composables/useAPI.ts:3:56) at Proxy.loadQuote (/home/ll3006/sample-project/stores/quote.ts:7:49) at Proxy.wrappedAction (/home/ll3006/sample-project/node_modules/pinia/dist/pinia.mjs:1379:26) at /home/ll3006/sample-project/app.js:17:19) at async callOnce (/home/ll3006/sample-project/node_modules/nuxt/dist/app/composables/once.js:25:3) at async setup (/home/ll3006/sample-project/app.js:18:25)
Which looks to me like loadQuote doesn't work as it can't access useNuxtApp anymore. useNuxtApp is ultimately used to load the api endpoint from the runtimeConfig, through a custom $fetch-like $api plugin. Using $fetch and manually hardcoding the endpoit works as expected. The problem therefore lies in the fact that only the first await seems to receive the nuxt context. Why does this happen? What causes loadQuote to lose access to the nuxt context? Can I work around this?
5 Replies
kapa.ai
kapa.ai2w ago
Hi @LL3006. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- You're experiencing a common issue with async context in Nuxt. This happens because Nuxt (like Vue) loses its context after the first await statement in an async function. Why This Happens The issue you're encountering is related to how Nuxt handles its context in async functions. As explained in the documentation:
On top of that, you must use them synchronously - that is, you cannot use await before calling a composable [...]
Vue and Nuxt Composables When you add the sleep function before loadQuote(), the Nuxt context is lost after the first await statement. This is a design limitation with composables in Nuxt and Vue. The Technical Explanation During component lifecycle, Nuxt tracks a temporary instance of nuxtApp via a global variable and unsets it in the same tick. This is essential for server rendering to avoid cross-request state pollution. After an await statement, the context is no longer available:
// Vue / Nuxt sets a global variable referencing to current component when calling setup()
async function setup() {
getCurrentInstance() // Works
await someAsyncOperation() // Vue unsets the context in same tick before async operation!
getCurrentInstance() // null
}
// Vue / Nuxt sets a global variable referencing to current component when calling setup()
async function setup() {
getCurrentInstance() // Works
await someAsyncOperation() // Vue unsets the context in same tick before async operation!
getCurrentInstance() // null
}
useNuxtApp - A Deeper Explanation of Context Solutions 1. Use runWithContext The most direct solution is to use runWithContext to restore the Nuxt context:
const nuxtApp = useNuxtApp();

await callOnce(async () => {
await sleep(100);
// Restore context before calling loadQuote
await nuxtApp.runWithContext(() => quote.loadQuote());
});
const nuxtApp = useNuxtApp();

await callOnce(async () => {
await sleep(100);
// Restore context before calling loadQuote
await nuxtApp.runWithContext(() => quote.loadQuote());
});
useNuxtApp - runWithContext 2. Use navigation mode for callOnce Since Nuxt v3.15, callOnce supports a navigation mode that might help in your case:
await callOnce(async () => {
await sleep(100);
await quote.loadQuote();
}, { mode: 'navigation' });
await callOnce(async () => {
await sleep(100);
await quote.loadQuote();
}, { mode: 'navigation' });
callOnce - Usage 3. Enable experimental async context support If you're using a recent version of Nuxt, you can try enabling the experimental native async context support:
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
asyncContext: true
}
})
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
asyncContext: true
}
})
This feature uses Node.js AsyncLocalStorage to make async context available natively to any nested async composable without needing a transform or manual passing/calling with context. Native Async Context 4. Restructure your code Another approach is to avoid nested async calls or structure your code differently:
// Pre-fetch what you need
const nuxtApp = useNuxtApp();

await callOnce(async () => {
// Do all async operations first
await sleep(100);

// Then do operations that need Nuxt context
await quote.loadQuote();
});
// Pre-fetch what you need
const nuxtApp = useNuxtApp();

await callOnce(async () => {
// Do all async operations first
await sleep(100);

// Then do operations that need Nuxt context
await quote.loadQuote();
});
Note on Try/Catch Blocks Be especially careful with try/catch blocks containing await statements, as they can cause additional issues with context restoration. The Nuxt documentation specifically mentions this as a problematic area:
The unjs/unctx transformation to automatically restore context seems buggy with try/catch statements containing await
Remaining Issues I hope one of these solutions helps resolve your issue! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@293794800979673088> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
LL3006
LL3006OP2w ago
@kapa.ai I see. Are there downsides to using the experimental asyncContext config?
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- There are a few potential downsides to using the experimental asyncContext feature in Nuxt: 1. Platform Limitations: The feature relies on Node.js AsyncLocalStorage API, which means it currently only works fully in Node.js and Bun environments. As mentioned in the GitHub PR:
This feature is limited to Node.js for now but there is an TC39 proposal ongoing to support native context in other platforms.
GitHub PR #20918 2. Experimental Status: As an experimental feature, it may have bugs, performance issues, or undergo API changes in future Nuxt versions. This could potentially cause breaking changes in your application when upgrading Nuxt. 3. First-level Composable Limitation: When using from <script setup>, you might still need to call the first-level composable with nuxtApp.runWithContext since Vue itself doesn't make async context available:
When using from <script setup>, we need to call first level composable with nuxtApp.runWithContext since Vue itself does not make async context available or has async context support.
GitHub PR #20918 4. Potential Performance Impact: Maintaining context across async boundaries could have a small performance overhead, though this is likely negligible in most applications. 5. Debugging Complexity: Issues related to async context can be harder to debug since they involve tracking state across asynchronous operations. Despite these potential downsides, the feature can significantly improve developer experience by eliminating the need to manually pass context or restructure code to accommodate Nuxt's context limitations. If you're working on a Node.js-based deployment and the benefits outweigh the risks for your use case, it might be worth trying. As with any experimental feature, I'd recommend testing thoroughly in a development environment before deploying to production. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@293794800979673088> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?