--- title: 'Transforming HTML with transformPage' slug: transform-page description: 'Rewrite rendered HTML before it is sent to the client using the transformPage callback.' --- ## `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`. ```ts // file: src/hooks.ts import type { Handle } from 'mochi-framework/hooks'; const greeting: Handle = async ({ event, resolve }) => { return resolve(event, { transformPage({ html }) { return html.replace('{{app.greeting}}', 'Welcome to Mochi!'); }, }); }; ``` ```html
{{app.greeting}}
{{mochi.body}} ``` The callback receives `{ html, done }` and returns `string | undefined | Promise`. Returning `undefined` replaces the body with an empty string. ```ts const banner: Handle = async ({ event, resolve }) => { return resolve(event, { async transformPage({ html }) { const message = await fetchBannerMessage(); return html.replace('{{app.banner}}', message); }, }); }; ``` Use it for per-request mutations the shell template can't express on its own — request-aware ``, nonce injection, A/B placeholder swaps: ```ts 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(' Do **NOT** use `transformPage` for static content that belongs in the HTML shell; instead, edit `src/shell.html` (or the default shell) so the markup ships without per-request work. Reserve transforms for values that genuinely depend on the request.