🍡 mochi
← All demos

Error Handling

The built-in default error page plus a handleError hook, rendered for uncaught render errors and unmatched routes.

Mochi catches any throw from a page's serverProps resolver or Svelte <script>, along with any request that doesn't match a route, and renders the built-in default error page. Pass your own errorPage to Mochi.serve() to replace it. The handleError hook runs for every such error — use it to log, forward to an error tracker, sanitize the message, or return a Response to short-circuit rendering entirely.

Try it:

  • /demos/error/500 the page's <script> throws during SSR
  • /demos/error/404 the serverProps resolver calls error(404, ...)
  • /does-not-exist no route matches — handleError fires with error: null, then the error page renders with status 404
  • /demos/error/redirect the page throws, but handleError returns Response.redirect(...) — you land back on this page instead of seeing the error component

A custom error component receives a single error prop with status, message, and (in development only) stack — typed as MochiErrorProps.

This site's handleError:

const handleError: HandleError = ({ error, event, status, message }) => {
  console.log(
    `[handleError] ${event.url.pathname}${status} "${message}" (error ${error ? 'present' : 'null'})`,
  );
  // error is null for unmatched routes / unknown form actions — guard before forwarding
  if (error && status >= 500) {
    console.error('[app]', event.url.pathname, error);
  }
  // Short-circuit: redirect this specific demo path instead of rendering the error page
  if (event.url.pathname === '/demos/error/redirect') {
    return Response.redirect(new URL('/demos/error', event.url), 302);
  }
};

Tail your dev server's output while clicking the links above — you'll see one [handleError] line per visit. Unmatched routes log error null; SSR throws log error present.

<script>
  import { highlightCode } from 'mochi-framework/highlight';

  const handleErrorSnippet = `const handleError: HandleError = ({ error, event, status, message }) => {
  console.log(
    \`[handleError] \${event.url.pathname} → \${status} "\${message}" (error \${error ? 'present' : 'null'})\`,
  );
  // error is null for unmatched routes / unknown form actions — guard before forwarding
  if (error && status >= 500) {
    console.error('[app]', event.url.pathname, error);
  }
  // Short-circuit: redirect this specific demo path instead of rendering the error page
  if (event.url.pathname === '/demos/error/redirect') {
    return Response.redirect(new URL('/demos/error', event.url), 302);
  }
};`;
  const handleErrorHtml = highlightCode(handleErrorSnippet, 'ts');
</script>

<div class="prose">
  <p>
    Mochi catches any throw from a page's <code>serverProps</code> resolver or Svelte
    <code>&lt;script&gt;</code>, along with any request that doesn't match a route, and renders the built-in default error page. Pass your own <code>errorPage</code> to
    <code>Mochi.serve()</code> to replace it. The
    <code>handleError</code> hook runs for every such error — use it to log, forward to an error tracker, sanitize the message, or return a <code>Response</code> to short-circuit rendering
    entirely.
  </p>

  <p class="lead">Try it:</p>

  <ul class="examples">
    <li>
      <a href="/demos/error/500"><code>/demos/error/500</code></a>
      <span>the page's <code>&lt;script&gt;</code> throws during SSR</span>
    </li>
    <li>
      <a href="/demos/error/404"><code>/demos/error/404</code></a>
      <span>the <code>serverProps</code> resolver calls <code>error(404, ...)</code></span>
    </li>
    <li>
      <a href="/does-not-exist"><code>/does-not-exist</code></a>
      <span>no route matches — <code>handleError</code> fires with <code>error: null</code>, then the error page renders with status 404</span>
    </li>
    <li>
      <a href="/demos/error/redirect"><code>/demos/error/redirect</code></a>
      <span>
        the page throws, but <code>handleError</code> returns <code>Response.redirect(...)</code> — you land back on this page instead of seeing the error component
      </span>
    </li>
  </ul>

  <p>
    A custom error component receives a single <code>error</code> prop with <code>status</code>,
    <code>message</code>, and (in development only) <code>stack</code> — typed as
    <code>MochiErrorProps</code>.
  </p>

  <p class="lead">This site's <code>handleError</code>:</p>

  <!-- eslint-disable-next-line svelte/no-at-html-tags -->
  {@html handleErrorHtml}

  <p>
    Tail your dev server's output while clicking the links above — you'll see one
    <code>[handleError]</code> line per visit. Unmatched routes log <code>error null</code>; SSR throws log <code>error present</code>.
  </p>
</div>
Styles
<style>
  .prose {
    font-size: 0.95rem;
    line-height: 1.6;
    color: var(--text);
  }

  .prose p {
    margin: 0 0 1rem;
  }

  .prose p.lead {
    margin-top: 0.5rem;
    margin-bottom: 0.5rem;
    font-weight: 600;
    color: var(--text);
  }

  .prose code {
    font-family: var(--font-mono);
    background: var(--surface-muted);
    padding: 0.12rem 0.4rem;
    border-radius: var(--radius-sm);
    font-size: 0.88em;
    color: var(--text);
  }

  .examples {
    list-style: none;
    margin: 0 0 1.25rem;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .examples li {
    display: flex;
    align-items: baseline;
    gap: 0.75rem;
    padding: 0.6rem 0.85rem;
    background: var(--surface-muted);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    flex-wrap: wrap;
  }

  .examples li a {
    text-decoration: none;
    flex-shrink: 0;
  }

  .examples li a code {
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
    transition:
      background 0.12s ease,
      color 0.12s ease,
      border-color 0.12s ease;
  }

  .examples li a:hover code {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent-soft-text);
  }

  .examples li span {
    color: var(--text-muted);
    font-size: 0.88rem;
  }
</style>