Files
crumbs/.agents/skills/sveltekit-structure/references/svelte-boundary.md
T
Frederic CabassutandClaude Opus 4.6 02e6b57764 chore: add agent skills for Svelte 5, SvelteKit, TypeScript, and Drizzle
Install project-level skills via skills.sh CLI:
- svelte5-best-practices (runes, snippets, events, migration)
- sveltekit-structure (routing, layouts, error boundaries)
- sveltekit-data-flow (load functions, form actions, serialization)
- typescript-best-practices (type-first development, discriminated unions)
- drizzle-migrations (SQLite schema, query patterns)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:11:36 +01:00

151 lines
3.4 KiB
Markdown

# svelte:boundary Component
> Available in Svelte 5.3+
## Two Purposes
1. **Error boundaries** - catch rendering errors
2. **Pending UI** - show loading state while `await` resolves
## Basic Error Boundary
```svelte
<svelte:boundary onerror={(e, reset) => console.error(e)}>
<RiskyComponent />
{#snippet failed(error, reset)}
<p>Error: {error.message}</p>
<button onclick={reset}>Try again</button>
{/snippet}
</svelte:boundary>
```
## Pending UI (Loading States)
> **⚠️ Known Bug:** `<svelte:boundary>` + `{@const await}` causes
> infinite navigation loops during client-side page transitions when
> pages share async queries. See
> [sveltejs/svelte#17717](https://github.com/sveltejs/svelte/issues/17717).
> Use `{#await}` blocks until this is fixed.
With `experimental.async: true` (Svelte 5.36+), `{@const await}` is
possible but **not safe for pages with shared queries or navigation**:
```svelte
<!-- ⚠️ Causes navigation loops - use {#await} instead -->
<svelte:boundary>
{#snippet pending()}
<LoadingSpinner />
{/snippet}
{@const data = await loadData()}
<DataView {data} />
</svelte:boundary>
```
**Safe alternative:**
```svelte
{#await loadData()}
<LoadingSpinner />
{:then data}
<DataView {data} />
{:catch error}
<p>Error: {error.message}</p>
{/await}
```
## Combined Error + Pending
Use `svelte:boundary` for **error catching only**, with `{#await}` for
async data:
```svelte
<svelte:boundary onerror={logError}>
{#snippet failed(error, reset)}
<p>Failed to load user</p>
<button onclick={reset}>Retry</button>
{/snippet}
{#await fetchUser()}
<p>Loading user...</p>
{:then user}
<UserProfile {user} />
{:catch error}
<p>Error: {error.message}</p>
{/await}
</svelte:boundary>
```
## What Gets Caught
**Caught:**
- Errors during rendering
- Errors in `$effect`
**NOT Caught:**
- Event handler errors (`onclick`, etc.)
- Errors after `setTimeout`
- Async errors outside boundary's await
## vs +error.svelte
| Feature | svelte:boundary | +error.svelte |
| -------- | ----------------------- | ------------- |
| Scope | Component subtree | Route segment |
| Reset | Built-in reset function | Navigate away |
| Pending | Yes (pending snippet) | No |
| Use case | Component-level | Page-level |
## Error Tracking Integration
```svelte
<svelte:boundary
onerror={(error, reset) => {
// Send to Sentry, LogRocket, etc.
errorTracker.captureException(error);
}}
>
<App />
{#snippet failed(error, reset)}
<ErrorFallback {error} {reset} />
{/snippet}
</svelte:boundary>
```
## Nested Boundaries
Inner boundary catches first:
```svelte
<svelte:boundary>
<!-- Outer fallback -->
{#snippet failed(e)}
<p>Outer caught: {e.message}</p>
{/snippet}
<svelte:boundary>
<!-- Inner fallback -->
{#snippet failed(e)}
<p>Inner caught: {e.message}</p>
{/snippet}
<ComponentThatMightFail />
</svelte:boundary>
</svelte:boundary>
```
## Key Points
- Use `svelte:boundary` for component-level error isolation
- Use `+error.svelte` for route-level error pages
- `pending` snippet shows only on **initial** load (not on refresh - no flicker)
- `failed` snippet replaces content on error
- `reset` function lets users retry
- Errors in event handlers are NOT caught
- Requires `experimental.async: true` in svelte.config.js for `{@const await}`
- **⚠️ Bug:** `{@const await}` + navigation causes infinite loops ([#17717](https://github.com/sveltejs/svelte/issues/17717)) — use `{#await}` instead