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

3.4 KiB

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: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. 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:

<!-- ⚠️ Causes navigation loops - use {#await} instead -->
<svelte:boundary>
	{#snippet pending()}
		<LoadingSpinner />
	{/snippet}

	{@const data = await loadData()}
	<DataView {data} />
</svelte:boundary>

Safe alternative:

{#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: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: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: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) — use {#await} instead