Hydration
After the server generates static HTML, the browser needs to "wake it up" — reconnecting event listeners, reactive Cells, and making the page interactive. This process is called hydration.
Basic Setup
In your client-side entry point, import hydrate from retend-server/client and pass it your router factory function:
// source/main.ts import { hydrate } from 'retend-server/client'; import { createRouter } from './router'; hydrate(createRouter);
The hydrate function:
- Finds the existing server-rendered HTML.
- Re-creates the router and matches the current URL.
- Attaches event listeners and reactive bindings to the existing elements.
- Dispatches a
hydrationcompletedevent onwindowwhen finished.
window.addEventListener('hydrationcompleted', () => { console.log('App is now interactive!'); });
Options
The hydrate function accepts an options object as its second argument:
hydrate(createRouter, { rootId: 'app', wrap(root) { return root; }, });
| Option | Default | Description |
|---|---|---|
rootId | "app" | The id of the root element that holds the server-rendered HTML. |
wrap | — | A function that wraps the app root before rendering. Useful for adding global providers. |
Wrapping the App
If your app needs a global provider (like a theme or auth context) that wraps the entire router, use the wrap option:
import { ThemeScope } from './scopes'; hydrate(createRouter, { wrap(root) { return <ThemeScope.Provider value="dark">{root}</ThemeScope.Provider>; }, });
Hydration Contract
hydrate uses the browser's current path, query, and hash as the initial router location. It hydrates roots marked with data-retend-hydration="1"; otherwise, it replaces the root contents with a client render.
Server and client structure must match. A structural mismatch rejects hydration; Retend does not recover individual nodes. Differing text and dangerouslySetInnerHTML values are updated on the claimed node. Once a dynamic range is claimed, normal reactive updates may run while other async boundaries are still hydrating.