Skip to content

ErrorBoundary

Props

resetKeys

unknown[] · default: —

When any value changes (compared with Object.is), the boundary auto-resets — same idea as react-error-boundary's resetKeys.

resetOnPropsChange

boolean · default: false

Reset whenever any prop reference changes, not just resetKeys.

beforeReset

() => void · default: —

Called right before a reset (auto or manual). Not named onReset — see note below.

isolate

boolean · default: true

false lets the error also propagate to the nearest ancestor <ErrorBoundary>.

maxRetries

number · default: unlimited

Once reached, the fallback slot's canRetry becomes false.

reporter

ErrorReporter | ErrorReporter[] · default: —

Reporter(s) invoked once per captured error — see Reporting adapters.

shouldCatch

(error: CapturedError) => boolean · default: —

Return false to let an error pass through this boundary untouched — no state change, no report, no fallback — exactly as if it weren't there. See Ignoring specific errors.

internalErrorPrefix

string · default: '[vue-error-boundary-kit]'

Prefix for the safety-net log emitted when your own beforeReset/reporter itself throws. Pass '' to omit it.

Why beforeReset, not onReset? This component also emits a reset event, and Vue derives onReset as that event's own listener prop. A declared prop with the identical name would collide with it: Vue's emit() looks up props.onReset independently of whether it's "really" a declared prop, so it would've fired twice per reset — and the second, emit-triggered call happens outside this package's own try/catch, defeating the recursion guard entirely if it throws. beforeReset avoids the collision structurally.

Events

error

(error: CapturedError)

Fired for every captured failure, including ones bubbled up from a descendant boundary with isolate: false.

reset

Fired on every manual or automatic reset.

Slots

default

Normal content.

fallback

{ error, reset, retry, retryCount, canRetry }

reset() clears retry count too; retry() increments retryCount and re-attempts default without resetting the count.

Exposed

Via a template ref: error, hasError, retryCount, canRetry, reset(), retry() — the same state and methods the fallback slot gets, but reachable from outside it (e.g. a retry control that lives elsewhere in the UI, or an ancestor recovering a boundary it doesn't render the fallback for). Calling reset()/retry() on a boundary that isn't in an error state is a harmless no-op.

vue
<script setup>
const boundary = ref()
</script>

<template>
  <ErrorBoundary ref="boundary">…</ErrorBoundary>
  <button @click="boundary?.reset()">Reset from elsewhere</button>
</template>

Retry with backoff

retry() is instant and unconditional — fine for most cases, but for a transient/network-ish failure, retrying the instant the button is clicked usually just fails again the same way. vue-error-boundary-kit/retry-backoff wraps a template ref's retry() with an increasing delay instead:

ts
import { createBackoffRetry } from 'vue-error-boundary-kit/retry-backoff'

const boundary = useTemplateRef('boundary')
const backoff = createBackoffRetry(boundary, { baseDelayMs: 1000, factor: 2, maxDelayMs: 30_000 })
vue
<ErrorBoundary ref="boundary">
  <template #fallback="{ error }">
    <button :disabled="backoff.isPending.value" @click="backoff.retry()">
      {{ backoff.isPending.value ? 'Retrying…' : 'Retry' }}
    </button>
  </template>
</ErrorBoundary>

The delay is computed from the boundary's own retryCount (baseDelayMs * factor ** retryCount, capped at maxDelayMs), so each successive attempt waits longer. cancel() clears a pending retry. Works with <AsyncBoundary>'s ref too — both expose the same { retry, retryCount } shape.

AsyncBoundary

Separate entry point (vue-error-boundary-kit/async-boundary) — not part of the core bundle, so it costs nothing if you don't import it. Combines <Suspense> and <ErrorBoundary>, which today you'd otherwise nest by hand:

ts
import { AsyncBoundary } from 'vue-error-boundary-kit/async-boundary'
vue
<AsyncBoundary :reset-keys="[userId]">
  <template #default>
    <UserProfile :id="userId" />
    <!-- async setup() / async components allowed -->
  </template>
  <template #loading>
    <Spinner />
  </template>
  <template #fallback="{ error, retry }">
    <ErrorState :message="error.message" @retry="retry" />
  </template>
</AsyncBoundary>

It's a composition, not a reimplementation: internally it's <ErrorBoundary> wrapping a <Suspense>, so it accepts every <ErrorBoundary> prop (resetKeys, maxRetries, reporter, shouldCatch, …) documented above, emits the same error/reset events, and exposes the same error/hasError/retryCount/canRetry/reset()/retry() via a template ref — all handled by the one onErrorCaptured implementation <ErrorBoundary> already has. The only thing it adds is the loading slot, rendered while the default slot's async dependencies are pending. retry()/reset() remount the default slot, so a retried async operation genuinely re-runs (the loading slot reappears while it does) rather than just re-showing stale state.