# Error boundary


The `FlareErrorBoundary` component uses Vue's `onErrorCaptured` hook to catch errors thrown by descendant components during setup, rendering, lifecycle hooks, watchers, and event handlers. When an error is caught, it is automatically reported to Flare with Vue-specific context including a structured component hierarchy.

## Basic usage

Wrap your component tree (or parts of it) in the `FlareErrorBoundary`:

```vue
<script setup>
import { FlareErrorBoundary } from '@flareapp/vue';
</script>

<template>
    <FlareErrorBoundary>
        <MyComponent />
    </FlareErrorBoundary>
</template>
```

Without a `fallback` slot, the boundary renders nothing when an error is caught.

## Displaying a fallback UI

The `fallback` slot is a scoped slot. Provide plain content for a static fallback:

```vue
<FlareErrorBoundary>
    <App />
    <template #fallback>
        <p>Something went wrong.</p>
    </template>
</FlareErrorBoundary>
```

Or use the slot props to render the error and let the user retry:

```vue
@verbatim
<FlareErrorBoundary>
    <App />
    <template #fallback="{ error, componentHierarchy, resetErrorBoundary }">
        <div>
            <h2>Something went wrong</h2>
            <p>{{ error.message }}</p>
            <pre>{{ componentHierarchy.join('\n') }}</pre>
            <button @click="resetErrorBoundary">Try again</button>
        </div>
    </template>
</FlareErrorBoundary>
@endverbatim
```

The `fallback` slot receives:

| Slot prop | Type | Description |
|------|------|-------------|
| `error` | `Error` | The error that was caught. |
| `componentHierarchy` | `string[]` | Component names from the erroring component up to the app root. |
| `componentHierarchyFrames` | `ComponentHierarchyFrame[]` | Structured frames with `component`, `file`, and optional `props`. |
| `componentProps` | `Record<string, unknown>` \| `undefined` | The erroring component's props. Only present when `attachProps` is enabled. |
| `resetErrorBoundary` | `() => void` | Clears the error state and re-renders the default slot. |

## Resetting the error boundary

You can automatically reset the boundary when certain values change using the `resetKeys` prop. When any value in the array changes (compared via `Object.is`), the boundary automatically resets and re-renders its children.

A common use case is resetting the boundary when the user navigates to a different page:

```vue
<script setup>
import { useRoute } from 'vue-router';
import { FlareErrorBoundary } from '@flareapp/vue';

const route = useRoute();
</script>

<template>
    <FlareErrorBoundary
        :reset-keys="[route.path]"
        :on-reset="(error) => {
            console.log('Navigated away from error, previous error:', error?.message);
        }"
    >
        <RouterView />
        <template #fallback>
            <p>Something went wrong.</p>
        </template>
    </FlareErrorBoundary>
</template>
```

The `onReset` callback fires when the boundary resets (either via `resetErrorBoundary()` from the fallback or via `resetKeys` changing). It receives the previous error, which lets you branch on what went wrong:

```vue
<script setup>
import { useQueryClient } from '@tanstack/vue-query';
import { FlareErrorBoundary } from '@flareapp/vue';

const queryClient = useQueryClient();
</script>

<template>
    <FlareErrorBoundary
        :on-reset="(error) => {
            console.log('Recovering from:', error?.message);
            queryClient.invalidateQueries();
        }"
    >
        <App />
        <template #fallback="{ resetErrorBoundary }">
            <button @click="resetErrorBoundary">Retry</button>
        </template>
    </FlareErrorBoundary>
</template>
```

## Lifecycle hooks

The error boundary provides three hooks that fire at different stages of the error reporting lifecycle. All three callbacks receive `{ error, instance, info }`, where `instance` is the Vue component that threw (or `null`) and `info` is Vue's lifecycle hook string (e.g. `"setup function"`, `"mounted hook"`, `"component event handler"`).

The `beforeSubmit` and `afterSubmit` callbacks additionally receive a `context` object with the following shape:

```ts
{
    vue: {
        info: string;                        // Vue-provided lifecycle hook string
        errorOrigin: ErrorOrigin;            // Normalized category
        componentName: string;
        componentProps?: Record<string, unknown>;
        componentHierarchy: string[];
        componentHierarchyFrames: {
            component: string;
            file: string | null;
            props?: Record<string, unknown>;
        }[];
        route?: RouteContext;                // Present when Vue Router is detected
    }
}
```

These types are exported from `@flareapp/vue` as `FlareVueContext` and `ComponentHierarchyFrame`.

### `beforeEvaluate`

Fires **before** the component hierarchy context is built. This is the place to attach custom context, tags, or user information to the Flare report:

```vue
<FlareErrorBoundary
    :before-evaluate="({ error, instance, info }) => {
        flare.addContext('user', { id: currentUser.id });
        flare.addContext('feature-flags', getActiveFlags());
    }"
>
    <App />
</FlareErrorBoundary>
```

### `beforeSubmit`

Fires **after** the component hierarchy context is built but **before** the error is reported to Flare. The callback must return a (possibly modified) context object. Use this to filter or enrich the report context:

```vue
<FlareErrorBoundary
    :before-submit="({ error, instance, info, context }) => {
        return {
            ...context,
            vue: {
                ...context.vue,
                componentHierarchy: context.vue.componentHierarchy.filter(
                    (name) => name !== 'ThirdPartyWrapper',
                ),
            },
        };
    }"
>
    <App />
</FlareErrorBoundary>
```

### `afterSubmit`

Fires **after** `flare.report()` is called. Note that the report is sent asynchronously, so this callback runs after the report is initiated, not after the network request completes. Use this for side effects like logging to a secondary service, showing a toast, or updating app state:

```vue
<FlareErrorBoundary
    :after-submit="({ error, instance, info, context }) => {
        console.error('Caught by FlareErrorBoundary:', error);
        console.error('Lifecycle info:', info);
        console.error('Reported context:', context);
    }"
>
    <App />
</FlareErrorBoundary>
```

## Filtering errors

The boundary hooks above are designed for adding context and running side effects; they cannot prevent an error from being reported. To filter or suppress errors, use the core JavaScript client hooks via `flare.configure()`.

Note that the boundary's `beforeEvaluate` and `beforeSubmit` are **different hooks** from `flare.configure({ beforeEvaluate, beforeSubmit })`. They share the same name, but have different signatures and capabilities. The execution order when both are configured is:

1. Boundary `beforeEvaluate` (adds context, cannot suppress)
2. `flare.report()` is called internally, which triggers:
3. Client `beforeEvaluate` via `flare.configure()` (**can** suppress by returning `false`)
4. Client `beforeSubmit` via `flare.configure()` (**can** suppress or modify the report)
5. Report is sent to Flare
6. Boundary `afterSubmit`

To suppress specific errors, use the client-level hooks:

```js
flare.configure({
    beforeEvaluate: (error) => {
        if (error.message.includes('Boring error')) {
            return false; // Don't report this error
        }

        return error;
    },
});
```

See the [client hooks documentation](/docs/javascript/errors/client-hooks) for more details.

## Using your own error boundaries

If you have a component that already uses `onErrorCaptured` to render a custom fallback UI, the error won't bubble up to `FlareErrorBoundary` and won't be reported automatically. You can manually report errors to Flare from your own boundary:

```vue
<script setup>
import { onErrorCaptured, ref } from 'vue';
import { flare } from '@flareapp/js';

const error = ref(null);

onErrorCaptured((err) => {
    flare.report(err);
    error.value = err;
    return false;
});
</script>
```

You can also combine Flare's boundary at the root of your app with your own boundaries further down the tree for specific sections.

## Capturing component props

Vue's `onErrorCaptured` gives the boundary access to the live component instance. You can capture the erroring component's props by enabling `attachProps`:

```vue
<FlareErrorBoundary :attach-props="true">
    <App />
</FlareErrorBoundary>
```

When enabled, props are attached both at `context.vue.componentProps` (the erroring component's props) and per-frame on `context.vue.componentHierarchyFrames` (each frame's own props).

Props capture is opt-in because props may contain sensitive data. Values are serialized with the following rules:

- Functions become `"[Function]"`, symbols become `"[Symbol]"`, bigints are converted to strings, circular references become `"[Circular]"`, class instances, `Date`, `RegExp`, `Map`, `Set`, etc. become `"[Object]"`.
- Symbol-keyed properties are dropped.
- Strings longer than 1000 characters are truncated, with a `"…[truncated N chars]"` suffix appended.
- Arrays longer than 100 items are trimmed, with a final `"[… N more items]"` entry appended.
- Objects with more than 100 keys are trimmed, with an extra `"…": "[N more keys]"` entry appended.

You can control serialization with two additional props:

| Prop | Default | Description |
|------|---------|-------------|
| `propsMaxDepth` | `2` | How deep nested objects and arrays are serialized before collapsing to `"[Object]"` / `"[Array]"`. |
| `propsDenylist` | `DEFAULT_PROPS_DENYLIST` | A `RegExp` tested against each key at every depth. Matching keys are replaced with `"[redacted]"`. By default, a custom value is **merged** with the built-in denylist (both patterns apply). Set `replaceDefaultDenylist` to `true` to use only your custom pattern. |

The default denylist covers common sensitive terms (`password`, `token`, `secret`, `authorization`, `cookie`, `api_key`, `session`, `csrf`, `ssn`, `card_number`, `cvv`, …). Since custom denylists extend the default automatically, you can simply pass additional patterns:

```vue
<FlareErrorBoundary :attach-props="true" :props-denylist="/internalId/i">
    <App />
</FlareErrorBoundary>
```

If you need to replace the default entirely (e.g. it matches too aggressively for your use case), pass `:replace-default-denylist="true"`:

```vue
<FlareErrorBoundary
    :attach-props="true"
    :props-denylist="/myCustomPattern/i"
    :replace-default-denylist="true"
>
    <App />
</FlareErrorBoundary>
```

## Component hierarchy context

When the error boundary catches an error, it walks the `$parent` chain of the erroring component and sends both a flat string array and structured frames in the report context:

```json
{
  "context": {
    "vue": {
      "info": "setup function",
      "errorOrigin": "setup",
      "componentName": "BuggyComponent",
      "componentHierarchy": [
        "BuggyComponent",
        "ParentPage",
        "AppLayout",
        "App"
      ],
      "componentHierarchyFrames": [
        { "component": "BuggyComponent", "file": "src/components/BuggyComponent.vue" },
        { "component": "ParentPage", "file": "src/pages/ParentPage.vue" },
        { "component": "AppLayout", "file": "src/layouts/AppLayout.vue" },
        { "component": "App", "file": "src/App.vue" }
      ]
    }
  }
}
```

The structured frames enable rich dashboard rendering and sourcemap resolution.

A few notes:

- Component names are read from `$options.__name` (set by the SFC compiler for `<script setup>`) or `$options.name`, falling back to `"AnonymousComponent"`.
- The `file` field comes from `$options.__file` and is only available in development builds. It is `null` in production.
- Traversal stops at the app root (where `$parent` is `null`) and is bounded at 50 entries as a safety net against pathological parent chains.

## Error origin

Vue's `info` string describes where the error was caught. The boundary also includes a normalized `errorOrigin` category, which is easier to filter and group on:

| `errorOrigin` | Example `info` strings |
|---------------|------------------------|
| `setup` | `setup function`, `ref function`, `async component loader` |
| `render` | `render function`, `component update`, `scheduler flush` |
| `lifecycle` | `mounted hook`, `beforeUnmount hook`, `updated hook`, `directive hook`, … |
| `event` | `native event handler`, `component event handler` |
| `watcher` | `watcher getter`, `watcher callback`, `watcher cleanup function` |
| `unknown` | Anything Vue emits that doesn't map to the above |

Both development strings and production single-letter codes are mapped.

## Vue Router context

If Vue Router is installed on the app, the boundary automatically detects it via `app.config.globalProperties.$router` and includes the current route in the report context. No configuration required.

```json
{
  "context": {
    "vue": {
      "route": {
        "name": "user-profile",
        "path": "/users/42",
        "fullPath": "/users/42?tab=settings",
        "params": { "id": "42" },
        "query": { "tab": "settings" },
        "hash": "",
        "matched": ["AppLayout", "UserProfile"]
      }
    }
  }
}
```

Route `params` and `query` are serialized with the same denylist as `componentProps`, so values keyed with sensitive names (e.g. `token`) are redacted.

The route's `fullPath` is also passed through URL redaction (using the same logic as `url.full`), so sensitive query-string values are redacted before being included in the report.

## Using the boundary with `flareVue()`

`FlareErrorBoundary` returns `false` from `onErrorCaptured`, which stops the error from propagating to `app.config.errorHandler`. When you use both the boundary and the [`flareVue` plugin](/docs/vue/errors/error-handling), errors caught by the boundary are reported to Flare exactly once, using the **boundary's** hooks and options. The plugin's hooks only fire for errors that are **not** caught by a boundary. Errors outside any boundary are still caught by the plugin.

## Props reference

| Prop | Type | Description                                                                             |
|------|------|-----------------------------------------------------------------------------------------|
| `resetKeys` | `unknown[]` | Values that trigger an automatic reset when changed (compared via `Object.is`).         |
| `onReset` | `(error: Error \| null) => void` | Called when the boundary is reset; receives the previous error.                         |
| `beforeEvaluate` | `({ error, instance, info }) => void` | Called before the component hierarchy context is built.                                 |
| `beforeSubmit` | `({ error, instance, info, context }) => FlareVueContext` | Called before submitting; must return a (possibly modified) context.                    |
| `afterSubmit` | `({ error, instance, info, context }) => void` | Called after `flare.report()` is called (the report is sent asynchronously).            |
| `attachProps` | `boolean` (default `false`) | Attach the erroring component's props, plus each frame's props, to the report context.  |
| `propsMaxDepth` | `number` (default `2`) | Depth limit used when serializing props.                                                |
| `propsDenylist` | `RegExp` (default `DEFAULT_PROPS_DENYLIST`) | Keys matching this expression are redacted at every depth.                              |
| `replaceDefaultDenylist` | `boolean` (default `false`) | When `true`, `propsDenylist` replaces the built-in denylist instead of extending it. |
