🍡 mochi

SSR framework for Svelte 5 + Bun with islands-based selective hydration

On this page

Error handling

Mochi renders an HTML error page for any uncaught error that escapes a page render: top-level SSR throws, error(status, ...) from serverProps or actions, malformed form bodies, unknown form actions, and unmatched routes. API routes return a JSON envelope instead. Island-level boundaries are scoped to hydratable islands — see Error boundaries.

The built-in error page: a large 500 above the message Internal Server Error, a Go home link, and a Stack trace section showing the thrown error
The built-in error page, shown when errorPage is omitted. The stack trace renders only under development: true.

Configure the page with errorPage on Mochi.serve(). Omit it to use the built-in component.

// file: src/index.ts
import { Mochi } from 'mochi-framework';

await Mochi.serve({
  errorPage: './src/Error.svelte',
  routes: {
    '/': Mochi.page('./src/Home.svelte'),
  },
});

errorPage

The component receives one error prop typed by MochiErrorProps.

<!-- file: src/Error.svelte -->
<script lang="ts">
  import type { MochiErrorProps } from 'mochi-framework';
  let { error }: MochiErrorProps = $props();
</script>

<h1>{error.status}</h1>
<p>{error.message}</p>
{#if error.stack}<pre>{error.stack}</pre>{/if}
FieldDescription
statusHTTP status — 404, 500, or whatever was passed to error()
messageHuman-readable message, safe to render
stackStack trace, populated only when development: true, else absent

Default behavior without errorPage:

  • Unmatched routes → 404 Not Found.
  • Uncaught throws in serverProps, page render, or an action handler → 500 Internal Server Error.
  • error(status, message?) thrown from any of these → that exact status, with the message defaulting to the canonical status text when omitted.

Request context in the error page

Since: 0.10.0 (not released yet): Before 0.10.0, unmatched-route 404s rendered the error page without a request context.

getRequestContext() works inside the error component — url, cookies, and locals behave as on any page, so a shared nav can read the current path. On unmatched-route 404s the context is minimal: params is empty because no route matched.

<!-- file: src/Error.svelte -->
<script lang="ts">
  import { getRequestContext, type MochiErrorProps } from 'mochi-framework';
  let { error }: MochiErrorProps = $props();
  const { url } = getRequestContext();
</script>

<h1>{error.status}</h1><p>No page at {url.pathname}</p>

handleError

Fires whenever the error page is about to render. Use it to log, forward to error tracking, or sanitize the message the user sees.

// file: src/index.ts
import type { HandleError } from 'mochi-framework';

const handleError: HandleError = ({ error, event, status, message }) => {
  if (error) tracker.capture(error, { path: event.url.pathname });
  if (status === 404) return Response.redirect(new URL('/', event.url), 302);
  if (status >= 500) return { status, message: 'Something went wrong.' };
};

await Mochi.serve({
  errorPage: './src/Error.svelte',
  handleError,
  routes: {
    '/': Mochi.page('./src/Home.svelte'),
  },
});

Return one of:

  • { status, message } — override either field passed to the error component.
  • a Response — short-circuit rendering (useful for redirects).
  • void — keep the defaults.

error is null when the condition did not come from a throw (unmatched routes, unknown form actions). Inspect it before forwarding so benign 4xx cases do not page on-call.

If the hook itself throws, Mochi logs the secondary error and renders the error page with the original status and message.

API error envelope

Mochi.api routes return { "error": { "message", "status" } } with the matching status code. Use MochiHttpError (typed throw via error()) or apiError() (typed return) to produce the envelope. See API routes.

Fallback behavior

If your errorPage throws during render, Mochi returns a plain-text response mentioning both the original error and the secondary render failure. The error page cannot crash the server.

See it in action

Live demos showing key concepts from this page