SSR framework for Svelte 5 + Bun with islands-based selective hydration
Error boundaries
Every mochi:hydrate and mochi:hydrate:visible island is auto-wrapped in <svelte:boundary>. A throw in one island no longer takes down the whole page render — the failed island is replaced by a small marker (or hidden in production) and the rest of the page continues. No opt-in, no configuration.
What’s wrapped
mochi:hydrate— wrapped.mochi:hydrate:visible— wrapped.mochi:defer— handled by the server-island endpoint instead (see below).- Top-level page render throws — go to the configured
errorPage, not to a boundary. Mochi does not insert a page-level boundary; if you want a section to degrade gracefully, author<svelte:boundary>yourself.
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 (synchronous script throws,
$effect,$derived) — caught viatransformErroronhydrate(). Note: client-side errors are logged to the browser console but do not emitisland:error(the event bus is server-side only). - Synchronous failures in the island’s bundle import or in
hydrate()itself — caught by a defensive try/catch and rendered as the same failure stub.
Visual behaviour
In development the failed island is replaced by a dashed-red <mochi-island-failure> marker showing the component name and error message. In production the element is hidden (display: none) so end users see a clean gap instead of a stack trace.
Server islands (mochi:defer)
The /island/:name endpoint try/catches the SSR render and returns 200 plus a <mochi-island-failure> stub. The 200 is intentional — it stops the client’s retry loop from hammering a deterministic failure. Whatever fallback children you passed stay visible until the response arrives.
Author your own boundary
Mochi makes <svelte:boundary> work during SSR — stock Svelte boundaries are no-ops on the server without transformError, which Mochi passes for you. Use it anywhere — wrap a hand-written non-island component, or wrap a chunk of a page when you want bespoke degradation:
{#snippet failed(error: Error)}
<p>Something went wrong: {error.message}</p>
{/snippet}
<svelte:boundary {failed}>
<SomePieceThatMightThrow />
</svelte:boundary>Observability — island:error event
Every server-side island failure emits an island:error event on mochiEvents. Subscribe to forward failures to error tracking:
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 (matches the island-id attribute); set for 'server' failures, undefined for 'hydratable' SSR failures |
kind | 'hydratable' (SSR throw inside a hydratable island) or 'server' (server-island endpoint render). The MochiIslandErrorKind type also reserves 'client-hydrate', but client-side errors are not currently emitted to the event bus. |
message | Error message — safe to forward |
stack | Stack trace; populated only when development: true |
See also
- Error handling — top-level page errors and the configured
errorPage. - Selective hydration, Lazy hydration, Server islands — the directives boundaries wrap.