🍡 mochi

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 });
});
FieldDescription
componentNameIsland component name.
islandIdPer-island id; set for 'server' failures, else undefined.
kind'hydratable' (SSR throw in a hydratable island) or 'server' (server-island render).
messageError message, safe to forward.
stackStack 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

See it in action

Live demos showing key concepts from this page