sidebar_position: 5
Hydration And Client Pickup
This page describes the current Tama story for server-rendered HTML becoming interactive on the client.
Current Status
Tama currently exposes serializers for SSR, but it does not expose a first-class public hydrate(...) API in this checkout.
That distinction matters:
- SSR means generating HTML on the server.
- Hydration means attaching client behavior to the existing server DOM without recreating it.
- Client re-inflation means rendering the app again on the client and replacing the server markup.
Tama supports the first and the third today. The second is not a documented public API yet.
What The Docs App Does
This docs app is server-rendered with WebJSXSerializerAsync and then booted on the client by inflating the app again and calling replaceChildren(...) on the root element.
That means the current repository example is client re-inflation, not DOM-preserving hydration.
TIP
Client re-inflation is often fast enough for typical docs sites, admin panels, and dashboards. The server DOM is replaced as a whole, so there is no diffing overhead, and the client can skip data fetching by serializing initial state into the HTML. Tama is 3-4x faster than React in this mode, so it is a good fit for many use cases even without hydration.
Supported Strategy 1: SSR Only
If the page does not need client-side interactivity after load, generate HTML and stop there.
import { WebJSXSerializerAsync } from "@denshya/proton"
const serializer = new WebJSXSerializerAsync
const html = await serializer.asyncToString(<App />)
This is the simplest and most stable path.
Supported Strategy 2: SSR Plus Fresh Client Mount
If the page needs interactivity, the safest documented Tama path today is to render on the server and then mount a fresh client view.
import { WebInflator } from "@denshya/proton"
const inflator = new WebInflator
const root = document.getElementById("root")!
root.replaceChildren(inflator.inflate(<App />))
Important: this replaces the server DOM under the root. It is not hydration.
Carrying Initial State Across The Boundary
If you do SSR plus fresh client mount, serialize your initial data explicitly.
Server
<script id="app-state" type="application/json">
{"user":{"name":"FrameMuse"}}
script>
Client
function readInitialState<T>(id: string): T | null {
const element = document.getElementById(id)
if (element == null) return null
return JSON.parse(element.textContent || "null") as T | null
}
const initialState = readInitialState<{ user: { name: string } }>("app-state")
const inflator = new WebInflator
document.getElementById("root")!.replaceChildren(
inflator.inflate(<App initialState={initialState} />)
)
This avoids duplicate data fetching when the client mounts.
Supported Strategy 3: Progressive Enhancement And Islands
Because Tama is rootless, it also works well for targeted client islands. Instead of booting the whole page, mount only the interactive parts.
Server HTML
<div id="search-island">
<form>
<input name="q" />
form>
div>
Client
const inflator = new WebInflator
const island = document.getElementById("search-island")
if (island != null) {
island.replaceChildren(inflator.inflate(<SearchWidget />))
}
Again, this is replacement, not node-preserving hydration, but it is often enough for docs sites, admin panels, and progressively enhanced pages.
When You Truly Need Hydration
If your requirement is preserving server-rendered DOM and attaching listeners in place, the current public Tama surface is not documenting that as a built-in workflow.
For now, treat true hydration as:
- a custom bridge you would build yourself
- or a feature area that still needs first-class library design
Recommendation
Use this decision rule today:
- static HTML only: serializer only
- interactive app shell: SSR plus fresh client mount
- partial interactivity: progressive islands with targeted mounts
- strict hydration requirement: custom solution for now
Continue with SSR for serializer details and Testing for validating client pickup behavior.