---
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
{{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.