nuxt
nuxt copied to clipboard
Bug: Handling errors with the NuxtErrorBoundary component
The NuxtErrorBoundary
component does not seem to work as expected:
The #error slot will receive error as a prop. (If you set error = null it will trigger re-rendering the default slot;
Example:
// app.vue
<template>
<NuxtErrorBoundary>
<NuxtPage/>
<template #error="{ error }">
<p>An error occurred: {{ error }}</p>
<button @click="() => clearError({ redirect: '/' })">
This will clear the error.
</button>
</template>
</NuxtErrorBoundary>
</template>
Problem 1:
The error is NOT cleared at all when the button is clicked. The error message still can be seen on the screen.
Problem 2:
The Home page is NOT rendered at all when the route is redirected to /
successfully. It is still rendering the error component and message.
Any ideas?
Hey! Could you kindly provide a reproduction via StackBlitz or CodeSandbox? 🙏
@manniL here it is:
https://stackblitz.com/edit/github-qsnf12?file=app.vue
the NuxtErrorBoundary
component does not work on StackBlitz at all. So you need to download the sample app and run it locally then you see the problems.
Thanks
Thanks!
Does it work fine with using it inside a page (so not app.vue
)?
@manniL nope. if you put that error component on /pages/about.vue
, you will get the following error on your Console:
[vue-router warn]: uncaught error during route navigation:
[vue-router.js?v=414e8b35:2490 TypeError: num.toUpperCase is not a function
Trying to use NuxtErrorBoundary
on a component/page level, and I dont think its preventing global error page from rendering. I'm throwing createError from API and trying to gracefully show it while not rendering the global error page. Hopefully this ifxes soon... I'm about to pull all my hair out.
Yep, Even though I use NuxtErrorBoundary
the full error page shows up.
Hi I have the same issue using NuxtErrorBoundary around NuxtPage 👍
<NuxtErrorBoundary>
<NuxtPage />
</NuxtErrorBoundary>
Any idea or workaround ?
Hey, work for me in app.vue
Any update about this issue ? When NuxtErrorBoundary
is used the full error page shows up.
With NuxtErrorBoundary
in app.vue, ssr: false
, and an error thrown from a page component, nothing appears at all, just an error in the console. The error will show properly on page navigation, but not on initial page load.
[Vue warn]
[Vue warn]: Unhandled error during execution of setup function
at <Index onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< undefined > >
at <RouteProvider key="/projects/817c4d302bdaae79da0a6778" routeProps=
Object { Component: {…}, route: {…} }
pageKey="/projects/817c4d302bdaae79da0a6778" ... >
at <BaseTransition onAfterLeave=
Array [ onAfterLeave() ]
mode="out-in" appear=false ... >
at <Transition onAfterLeave=
Array [ onAfterLeave() ]
name="page" mode="out-in" >
at <RouterView name=undefined route=undefined >
at <NuxtPage>
at <[projectId] onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref<
Proxy { <target>: Proxy, <handler>: {…} }
> >
at <RouteProvider key="/projects/817c4d302bdaae79da0a6778" routeProps=
Object { Component: {…}, route: {…} }
pageKey="/projects/817c4d302bdaae79da0a6778" ... >
at <BaseTransition onAfterLeave=
Array [ onAfterLeave() ]
mode="out-in" appear=false ... >
at <Transition onAfterLeave=
Array [ onAfterLeave() ]
name="page" mode="out-in" >
at <RouterView name=undefined route=undefined >
at <NuxtPage>
at <Anonymous onError=fn<logError> >
at <App key=2 >
at <NuxtRoot> chunk-3NMN3MUW.js:1381
Error
Uncaught (in promise) Error: Project not found
NuxtJS 3
setup index.vue:20
callWithErrorHandling chunk-3NMN3MUW.js:1580
setupStatefulComponent chunk-3NMN3MUW.js:7383
setupComponent chunk-3NMN3MUW.js:7346
mountComponent chunk-3NMN3MUW.js:6033
processComponent chunk-3NMN3MUW.js:6011
patch chunk-3NMN3MUW.js:5700
componentUpdateFn chunk-3NMN3MUW.js:6128
run chunk-3NMN3MUW.js:405
update chunk-3NMN3MUW.js:6220
setupRenderEffect chunk-3NMN3MUW.js:6228
mountComponent chunk-3NMN3MUW.js:6046
processComponent chunk-3NMN3MUW.js:6011
patch chunk-3NMN3MUW.js:5700
mountSuspense chunk-3NMN3MUW.js:2417
process chunk-3NMN3MUW.js:2397
patch chunk-3NMN3MUW.js:5704
componentUpdateFn chunk-3NMN3MUW.js:6128
run chunk-3NMN3MUW.js:405
update chunk-3NMN3MUW.js:6220
setupRenderEffect chunk-3NMN3MUW.js:6228
mountComponent chunk-3NMN3MUW.js:6046
processComponent chunk-3NMN3MUW.js:6011
patch chunk-3NMN3MUW.js:5700
componentUpdateFn chunk-3NMN3MUW.js:6128
run chunk-3NMN3MUW.js:405
update chunk-3NMN3MUW.js:6220
setupRenderEffect chunk-3NMN3MUW.js:6228
mountComponent chunk-3NMN3MUW.js:6046
processComponent chunk-3NMN3MUW.js:6011
patch chunk-3NMN3MUW.js:5700
componentUpdateFn chunk-3NMN3MUW.js:6128
run chunk-3NMN3MUW.js:405
update chunk-3NMN3MUW.js:6220
setupRenderEffect chunk-3NMN3MUW.js:6228
mountComponent chunk-3NMN3MUW.js:6046
processComponent chunk-3NMN3MUW.js:6011
patch chunk-3NMN3MUW.js:5700
componentUpdateFn chunk-3NMN3MUW.js:6128
run chunk-3NMN3MUW.js:405
update chunk-3NMN3MUW.js:6220
setupRenderEffect chunk-3NMN3MUW.js:6228
mountComponent chunk-3NMN3MUW.js:6046
processComponent chunk-3NMN3MUW.js:6011
patch chunk-3NMN3MUW.js:5700
mountChildren chunk-3NMN3MUW.js:5845
mountElement chunk-3NMN3MUW.js:5777
processElement chunk-3NMN3MUW.js:5764
patch chunk-3NMN3MUW.js:5698
componentUpdateFn chunk-3NMN3MUW.js:6128
run chunk-3NMN3MUW.js:405
update chunk-3NMN3MUW.js:6220
setupRenderEffect chunk-3NMN3MUW.js:6228
registerDep chunk-3NMN3MUW.js:2696
promise callback*registerDep chunk-3NMN3MUW.js:2682
mountComponent chunk-3NMN3MUW.js:6039
processComponent chunk-3NMN3MUW.js:6011
patch chunk-3NMN3MUW.js:5700
componentUpdateFn chunk-3NMN3MUW.js:6128
run chunk-3NMN3MUW.js:405
update chunk-3NMN3MUW.js:6220
setupRenderEffect chunk-3NMN3MUW.js:6228
mountComponent chunk-3NMN3MUW.js:6046
processComponent chunk-3NMN3MUW.js:6011
patch chunk-3NMN3MUW.js:5700
mountSuspense chunk-3NMN3MUW.js:2417
process chunk-3NMN3MUW.js:2397
patch chunk-3NMN3MUW.js:5704
componentUpdateFn chunk-3NMN3MUW.js:6128
run chunk-3NMN3MUW.js:405
update chunk-3NMN3MUW.js:6220
setupRenderEffect chunk-3NMN3MUW.js:6228
mountComponent chunk-3NMN3MUW.js:6046
processComponent chunk-3NMN3MUW.js:6011
patch chunk-3NMN3MUW.js:5700
componentUpdateFn chunk-3NMN3MUW.js:6128
run chunk-3NMN3MUW.js:405
update chunk-3NMN3MUW.js:6220
setupRenderEffect chunk-3NMN3MUW.js:6228
mountComponent chunk-3NMN3MUW.js:6046
processComponent chunk-3NMN3MUW.js:6011
patch chunk-3NMN3MUW.js:5700
componentUpdateFn chunk-3NMN3MUW.js:6128
run chunk-3NMN3MUW.js:405
index.mjs:100:5
This component doesn't seem to have anything
FWIW, I'm using a home-grown components/ErrorBoundary.vue
:
<script setup lang="ts">
const error = ref<Error>()
function clearError() {
error.value = undefined
}
onErrorCaptured(err => {
error.value = err
return false
})
const route = useRoute()
watch(
() => route.fullPath,
() => {
error.value = undefined
},
)
</script>
<template>
<slot v-if="!error" />
<slot v-else name="error" :error="error" :clear-error="clearError" />
</template>
Features:
- does what
NuxtErrorBoundary
advertises - works with async components
- resets automatically on route change
- can be used on
parent.vue
alongsideparent/child1.vue
andparent/child2.vue
(handles crash in child1 and allows to switch to child2)
This is how one can use it with nuxt-page
:
<error-boundary>
<nuxt-page v-bind="$attrs" />
<template #error="{ error, clearError }">
<p>{{ error }}</p>
<button @click="clearError">Try again</button>
</template>
</error-boundary>
Thanks @IlyaSemenov. Nuxt source clearly doesn't do what it advertises. There's no route watching in there to clear errors like your homegrown version does.
However, even with your solution, I have to click a NuxtLink twice to get the route watcher to actually fire. For some reason the first route change does not register with the watcher even though I see the route change in address bar. Certainly nothing wrong with your example. It's either me, or some quirk of route/watcher.
Same issue here. When navigating to another page via any means (NuxtLink
, navigateTo
, clearError
with redirect etc), the error persists. The docs say:
If you navigate to another route, the error will be cleared automatically.
But i'm not seeing this behavior.
The only thing that reliably clears the error is setting the error
ref passed to the slot to undefined
/null
:
<NuxtErrorBoundary>
<template #error="{ error }">
<button @click="error.value = null">clear error</button>
</template>
<!-- ... -->
</NuxtErrorBoundary>
but this is far from ideal.
@IlyaSemenov's solution is an improvement, but as @mvandiest describes it requires two clicks on a NuxtLink.
OK, i've done a little more testing. If i put the NuxtErrorBoundary
within a page, the error does get cleared on route change. My issue (perhaps everyone else's too?) seems to be related to the error boundary being placed in a layout.
i tried clearError function at many different places but it worked NEVER! Apart from that useError always returns undefined
Here is my own take on an ErrorBoundary
component:
<script lang="ts">
let handlerSetup = false
const handlers: Array<() => void> = []
</script>
<script lang="ts" setup>
const error = shallowRef<any>(null)
onErrorCaptured((err) => {
// eslint-disable-next-line no-console
console.log('error', err)
error.value = err
return false
})
const router = useRouter()
async function clearError(options: { redirect?: any } = {}) {
if (options.redirect) {
await router.push(options.redirect)
}
error.value = null
}
if (!handlerSetup) {
router.beforeEach(() => {
handlers.forEach(handler => handler())
})
handlerSetup = true
}
handlers.push(clearError)
onBeforeUnmount(() => {
const index = handlers.indexOf(clearError)
if (index !== -1) {
handlers.splice(index, 1)
}
})
</script>
<template>
<slot
v-if="!error"
/>
<slot
v-else
name="error"
:error="error"
:clear-error="clearError"
/>
</template>
Just tried our the NuxtErrorBoundary component and it didn't work 🥲.
<template>
<div class="flex h-full">
<div class="flex h-full w-[270px] flex-col gap-1 rounded-lg p-2 py-4">
<m-left-nav-menu :links="links"> </m-left-nav-menu>
</div>
<div class="flex grow flex-col gap-4 p-4 lg:p-8">
<m-error-boundary>
<nuxt-page />
<template #error="{ error }">
<m-error :error />
</template>
</m-error-boundary>
</div>
</div>
</template>
When I use the NuxtErrorBoundary component my m-error component does not work well at all. The contents of the error like {{error.statusCode}}
don't show up.
Everything works as expected with @Akryum's ErrorBoundary component. But, I am building with ssr:false
I see a flash of the page before the error comes up 🥲
Also when I encounter an error and the error boundary is shown, a page refresh would cause a blank page.
FWIW, I'm using a home-grown
components/ErrorBoundary.vue
:<script setup lang="ts"> const error = ref<Error>() function clearError() { error.value = undefined } onErrorCaptured(err => { error.value = err return false }) const route = useRoute() watch( () => route.fullPath, () => { error.value = undefined }, ) </script> <template> <slot v-if="!error" /> <slot v-else name="error" :error="error" :clear-error="clearError" /> </template>
Features:
- does what
NuxtErrorBoundary
advertises- works with async components
- resets automatically on route change
- can be used on
parent.vue
alongsideparent/child1.vue
andparent/child2.vue
(handles crash in child1 and allows to switch to child2)This is how one can use it with
nuxt-page
:<error-boundary> <nuxt-page v-bind="$attrs" /> <template #error="{ error, clearError }"> <p>{{ error }}</p> <button @click="clearError">Try again</button> </template> </error-boundary>
hi. is this solution works on ssr?
Edit: Yes it works perfect both in csr and ssr mode. Why this solution is not in the documents and everywhere??
I'm also running into this but I am trying to use the NuxtErrorBoundary
in a layout. Is it expected that NuxtErrorBoundary
works in a layout? As far as I can tell there is no work-around for using it in a layout, the component examples here don't work for me.
Edit: I need to get this working, so happy to look into a PR to support layouts, but I'd like to get a better idea on what the expected behavior is here.