Error boundary
Catches a render error from anything inside it and shows a fallback instead of taking the page down.
<script setup lang="ts">
import ErrorBoundary from '@shared/components/feedback/ErrorBoundary.vue'
import { Button } from '@shared/components/ui/button'
import { ref } from 'vue'
const broken = ref(false)
const Report = {
props: { broken: { type: Boolean, default: false } },
setup(props: { broken: boolean }) {
return () => {
if (props.broken)
throw new Error('The report could not be built: revenue is missing for March.')
return 'Revenue report for March: $482,100 across 1,204 invoices.'
}
},
}
</script>
<template>
<div class="space-y-3">
<Button size="sm" variant="outline" @click="broken = !broken">
{{ broken ? 'Fix the data' : 'Break the report' }}
</Button>
<div class="rounded-lg border bg-card p-4 text-sm">
<ErrorBoundary :key="String(broken)">
<Report :broken="broken" />
</ErrorBoundary>
</div>
</div>
</template>Usage
Wrap the part of a page that can fail on its own: a chart fed by a third-party feed, a widget on a dashboard, a panel whose data may be half-written. Everything around it keeps working.
Retry clears the caught error, so the slot mounts again from scratch. If the cause has not changed, it will fail again, which is the honest outcome. Report is a hook for an error-reporting service; it logs today.
What it catches, and what it does not
It uses onErrorCaptured and returns false, which stops the error propagating further up.
| Where the error comes from | Caught here |
|---|---|
| A descendant's render function or template | Yes |
A descendant's lifecycle hook, such as onMounted | Yes |
| A click handler or another event handler | No. It reaches the app's global handler. |
| A rejected promise that nothing awaits | No |
An event handler's error is not a render error, so put the try/catch where the call is made. See useApiError for the API case.
Every caught error is written to the console as [ErrorBoundary], which is the hook an error-reporting service goes on. Pressing Report logs a second line. That is why this page's console is not silent once you break the example.
API
Source: src/shared/components/feedback/ErrorBoundary.vue
Props
None.
Emits
None.
Slots
| Slot | Description |
|---|---|
default | The subtree to protect. Replaced by the fallback while an error is held. |
Accessibility
- The fallback is
role="alert", so replacing the content is announced rather than happening silently. - The heading is an
<h2>and the error's own message is printed under it, so the reader is told what failed, not only that something did. - Retry and Report are ordinary buttons in the tab order, so recovering never needs a mouse.