SSR framework for Svelte 5 + Bun with islands-based selective hydration
transformPage
Pass transformPage to resolve(event, { transformPage }) inside a Handle to rewrite the rendered HTML before it ships. It runs once per response, only on text/html bodies, with the full HTML string and done: true.
// file: src/hooks.ts
import type { Handle } from 'mochi-framework';
const greeting: Handle = async ({ event, resolve }) => {
return resolve(event, {
transformPage({ html }) {
return html.replace('{{app.greeting}}', 'Welcome to Mochi!');
},
});
};<!-- file: src/shell.html -->
<body>
<header>{{app.greeting}}</header>
{{mochi.body}}
</body>The callback receives { html, done } and returns string | undefined | Promise<string | undefined>. Returning undefined replaces the body with an empty string.
Use it for per-request mutations the shell template cannot express: a request-aware <html lang>, nonce injection, or A/B placeholder swaps.
const lang: Handle = async ({ event, resolve }) => {
const locale = event.request.headers.get('accept-language')?.slice(0, 2) ?? 'en';
return resolve(event, {
transformPage({ html }) {
return html.replace('<html', `<html lang="${locale}"`);
},
});
};Mochi invokes the callback once with the full HTML, not chunk by chunk. done is always true. Treat it as a whole-document transform.
When sequence() chains handlers, transforms run inner-most first and outer-most last. The handler closest to the route sees the original HTML. The outermost wraps the final result.