# Error boundary


The `FlareErrorBoundary` component wraps Svelte's native `<svelte:boundary>` to catch errors during rendering and report them to Flare with Svelte-specific context including the component name, component hierarchy, and error origin.

## Basic usage

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

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

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

Without a `failed` snippet, the boundary renders nothing when an error is caught.

## Displaying a fallback UI

Use the `failed` snippet to render a fallback when an error is caught. The snippet receives the caught `error` and a `reset` function to clear the error state:

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

<FlareErrorBoundary>
    <App />
    {#snippet failed(error, reset)}
        <div>
            <h2>Something went wrong</h2>
            <p>{error.message}</p>
            <button onclick={reset}>Try again</button>
        </div>
    {/snippet}
</FlareErrorBoundary>
```

## Resetting the error boundary

You can automatically reset the error boundary when certain values change using the `resetKeys` prop. When any value in the array changes between renders (compared per-index 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:

```svelte
<script>
    import { page } from '$app/state';
    import { FlareErrorBoundary } from '@flareapp/svelte';

    let { children } = $props();
</script>

<FlareErrorBoundary
    resetKeys={[page.url.pathname]}
    onReset={(error) => {
        console.log('Navigated away from error, previous error:', error?.message);
    }}
>
    {@render children()}
    {#snippet failed(error, reset)}
        <p>Something went wrong.</p>
    {/snippet}
</FlareErrorBoundary>
```

The `onReset` callback fires when the error boundary resets (either via `reset()` from the `failed` snippet or via `resetKeys` changing). It receives the previous error, allowing conditional cleanup.

## Lifecycle hooks

The error boundary provides three hooks that fire at different stages of the error reporting lifecycle. The `beforeSubmit` and `afterSubmit` callbacks receive a `context` object with the following shape:

```ts
{
    svelte: {
        componentName: string | null;
        componentHierarchy: string[];
        errorOrigin: 'render' | 'event' | 'effect' | 'unknown';
    }
}
```

Both types are exported from `@flareapp/svelte` as `FlareSvelteContext` and `SvelteErrorOrigin`.

The callbacks are not wrapped in a `try`/`catch`. If a hook throws, the boundary itself will crash, so keep the logic defensive.

### `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:

```svelte
<script>
    import { flare } from '@flareapp/js';
    import { FlareErrorBoundary } from '@flareapp/svelte';
</script>

<FlareErrorBoundary
    beforeEvaluate={({ error }) => {
        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 receives the `context` and must return a (possibly modified) context object. Use this to filter or enrich the report context:

```svelte
<FlareErrorBoundary
    beforeSubmit={({ error, context }) => {
        return {
            ...context,
            svelte: {
                ...context.svelte,
                componentHierarchy: context.svelte.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:

```svelte
<FlareErrorBoundary
    afterSubmit={({ error, context }) => {
        console.error('Caught by FlareErrorBoundary:', error);
        console.error('Reported context:', context);
    }}
>
    <App />
</FlareErrorBoundary>
```

## Filtering errors

The error 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 `<svelte:boundary>` to catch errors, the error won't bubble up to `FlareErrorBoundary` and won't be reported automatically. You can use [`createFlareErrorHandler()`](/docs/svelte/errors/error-handler) to report errors from your own boundary:

```svelte
<script>
    import { createFlareErrorHandler } from '@flareapp/svelte';

    const handleError = createFlareErrorHandler();
</script>

<svelte:boundary onerror={handleError}>
    <slot />
    {#snippet failed(error, reset)}
        <p>Something went wrong. <button onclick={reset}>Retry</button></p>
    {/snippet}
</svelte:boundary>
```

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

## Component hierarchy

When the error boundary catches an error, it sends Svelte-specific context including the component name, the full component hierarchy, and the error origin:

```json
{
  "context": {
    "svelte": {
      "componentName": "BuggyComponent",
      "componentHierarchy": [
        "BuggyComponent",
        "NestedParent",
        "DashboardPage",
        "+layout"
      ],
      "errorOrigin": "render"
    }
  }
}
```

The full component hierarchy is available when the [Flare preprocessor](#enabling-the-component-hierarchy) is enabled. Without the preprocessor, the hierarchy falls back to stack frame extraction and will typically contain only the component that threw.

### Enabling the component hierarchy

Svelte does not expose a public API to walk the component tree. To work around this, `@flareapp/svelte` ships a Svelte preprocessor that injects a lightweight registration call into every component. This builds a parent-child chain using Svelte's context API (`setContext` / `getContext`), which the error boundary reads when an error is caught.

The easiest way to add it is by wrapping your config with `withFlareConfig` in `svelte.config.js`:

```js
import { withFlareConfig } from '@flareapp/svelte/config';

export default withFlareConfig({
    // your existing config (kit, compilerOptions, etc.)
});
```

`withFlareConfig` automatically injects the preprocessor alongside any existing preprocessors. It won't inject it twice if it's already present.

You can also use `flarePreprocessor()` directly for manual control:

```js
import { flarePreprocessor } from '@flareapp/svelte/config';

export default {
    preprocess: [flarePreprocessor()],
};
```

If you already have other preprocessors (e.g. `vitePreprocess`), add `flarePreprocessor()` to the array alongside them.

### How it works

The preprocessor adds two lines to the top of every component's `<script>` block at build time:

```js
import { __flareRegisterComponent } from '@flareapp/svelte';
__flareRegisterComponent('ComponentName', 'src/lib/ComponentName.svelte');
```

At runtime, each component:

1. Reads the parent's registration via `getContext()`,
2. Creates a node `{ name, file, parent }` linking to that parent,
3. Registers itself via `setContext()` so its children can find it.

When the error boundary catches an error, it looks up the erroring component (identified from the error's stack trace) in the registry and walks the `.parent` chain to build the full hierarchy.

### Trade-offs

The preprocessor is **optional**. Everything works without it, but the component hierarchy will be limited to what the stack trace reveals (usually one component).

| | With preprocessor | Without preprocessor |
|---|---|---|
| Component hierarchy | Full parent chain from error to root | Only components visible in the stack trace (usually 1) |
| Runtime cost | One `setContext` + one `getContext` per component mount | None |
| Bundle size | ~2 lines injected per component | No change |
| Build step | Wrap config with `withFlareConfig` or add preprocessor to `svelte.config.js` | No build changes |

### Excluding components

If you want to skip specific files (e.g. a design system library), pass an `exclude` regex:

```js
// With withFlareConfig:
withFlareConfig({ /* ... */ }, {
    exclude: /node_modules|\.stories\.svelte/,
})

// Or with flarePreprocessor directly:
flarePreprocessor({
    exclude: /node_modules|\.stories\.svelte/,
})
```

### Without the preprocessor

Without the preprocessor, component names are extracted from the error's stack frames by looking for `.svelte` file references. In development builds, `functionName` is preferred (more reliable). In production builds, the file name is used as a fallback. `componentName` is the first component found, or `null` if no `.svelte` file could be identified.

## Error origin

The error origin is determined by analyzing stack frame patterns. This heuristic classification helps with filtering and grouping errors:

| Error Origin | Description |
|---------------|-------------|
| `event` | Error originated from an event handler (e.g. `onclick`, `onchange`, `addEventListener`) |
| `effect` | Error originated from an asynchronous effect (e.g. `queueMicrotask`, `Promise.then`, `MutationObserver`) |
| `render` | Error occurred during rendering (detected by `.svelte` files in the stack with no event/effect patterns) |
| `unknown` | No Svelte-related frames found in the stack |

The priority order is: event > effect > render > unknown.

`FlareErrorBoundary` never produces `event`, because `<svelte:boundary>` only catches errors from rendering and effects; it never sees an error thrown from an event handler. The `event` origin is reachable through `createFlareErrorHandler`, which is wired up somewhere else in your app, not through the boundary.

## Props reference

| Prop | Type | Description |
|------|------|-------------|
| `children` | `Snippet` | The child content to render inside the boundary. |
| `failed` | `Snippet<[error: Error, reset: () => void]>` | Snippet to display when an error is caught. Receives the `error` and a `reset` function. |
| `resetKeys` | `unknown[]` | Values that trigger an automatic reset when changed (compared per-index via `Object.is`). |
| `flare` | `Flare` | The `Flare` instance to report through. Defaults to the shared instance from `@flareapp/js`. Set this when you're managing your own instance, or when using `@flareapp/svelte/inject`. |
| `beforeEvaluate` | `({ error }) => void` | Called before the component context is built. |
| `beforeSubmit` | `({ error, context }) => FlareSvelteContext` | Called before submitting. Return a (possibly modified) context. |
| `afterSubmit` | `({ error, context }) => void` | Called after `flare.report()` is called (the report is sent asynchronously). |
| `onReset` | `(error: Error \| null) => void` | Called when the error boundary is reset; receives the previous error (or `null`). |
