SSR framework for Svelte 5 + Bun with islands-based selective hydration
On this page
Error boundaries
Mochi auto-wraps every mochi:hydrate and mochi:hydrate:visible island in <svelte:boundary>. A throw inside one island no longer takes down the page render. Mochi marks the failed island with a <mochi-island-failure> stub, and the rest of the page continues. No opt-in, no configuration.
What is wrapped
mochi:hydrate— wrapped.mochi:hydrate:visible— wrapped.mochi:defer— handled by the server-island endpoint, not by a boundary.- Top-level page render throws — go to the configured
errorPage. See Error handling.
What gets caught
- Synchronous SSR throws inside the island.
- Async SSR throws (
await Promise.reject(...)in a top-level<script>). - Client-side throws after hydration (
$effect,$derived, synchronous script throws). - A failure in the island’s bundle import or in hydration itself.
A client-side throw logs to the browser console but does not emit island:error. The event bus is server-side only.
Visual behavior
Since: 0.10.0 (not released yet): Before 0.10.0 a client-side hydration failure replaced the island's server-rendered markup with the marker.
In development, the <mochi-island-failure> marker is a dashed-red box showing the component name and error message. In production, it is hidden with display: none.
What the marker replaces depends on where the failure happened. An SSR throw renders the marker instead of the island. A client-side failure — the bundle import, the CSS load, the props payload, or hydrate() itself — keeps the server-rendered markup and appends the marker after it. The static HTML stays on screen; it just never becomes interactive.
Server islands (mochi:defer)
A failed server island returns a <mochi-island-failure> stub with status 200. The 200 is intentional — a 5xx would trigger the client’s retry loop against a deterministic failure. Your fallback children stay visible until the response arrives.
Author your own boundary
<svelte:boundary> works during SSR, so you can use it anywhere:
<!-- file: src/SomePage.svelte -->
{#snippet failed(error: Error)}
<p>Something went wrong: {error.message}</p>
{/snippet}
<svelte:boundary {failed}>
<SomePieceThatMightThrow />
</svelte:boundary>island:error event
Every server-side island failure emits island:error on mochiEvents. Subscribe to forward failures to error tracking:
// file: src/index.ts
import { mochiEvents } from 'mochi-framework';
mochiEvents.on('island:error', ({ componentName, kind, message, stack }) => {
tracker.capture(message, { componentName, kind, stack });
});| Field | Description |
|---|---|
componentName | Island component name. |
islandId | Per-island id; set for 'server' failures, else undefined. |
kind | 'hydratable' (SSR throw in a hydratable island) or 'server' (server-island render). |
message | Error message, safe to forward. |
stack | Stack trace, populated only when development: true. |
MochiIslandErrorKind also reserves 'client-hydrate', but client-side errors are not currently emitted to the event bus.
See also
- Error handling — top-level page errors and the configured
errorPage. - Selective hydration, Lazy hydration, Server islands — the directives boundaries wrap.
See it in action
Live demos showing key concepts from this page