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/500the page's<script>throws during SSR/demos/error/404theserverPropsresolver callserror(404, ...)/does-not-existno route matches —handleErrorfires witherror: null, then the error page renders with status 404/demos/error/redirectthe page throws, buthandleErrorreturnsResponse.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><script></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><script></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>