Hydration in Helium
Choose when custom components become interactive, and safely keep static blocks as server HTML.
Hydration attaches browser behavior to server-rendered HTML. In Helium, custom components hydrate by default, including code whose hooks or events are hidden behind aliases. Source detection supplies hints; it does not prove that a component is static.
Set a strategy
Place hydrate beside type in a custom component's meta:
export const meta = {
type: "custom",
title: "Delivery note",
hydrate: "never",
props: { message: "Delivery to your door" },
propsSchema: {
message: { type: "string", name: "Message" },
},
} as const;
function App() {
return <p>{props.message}</p>;
}| Strategy | Behavior |
|---|---|
| Omitted | Default scheduling: chrome/top block uses idle; remaining cart/checkout content uses load; other islands use visible |
load | Begin hydration immediately |
idle | Hydrate at browser idle time; pointer activity can wake it sooner |
visible | Hydrate near the viewport; pointer activity or a timeout also wakes it |
interaction | Hydrate on pointer, focus or keyboard activity, with an idle fallback |
never | Keep this component's server HTML; attach no browser handlers and run no effects |
The compiler also accepts the legacy props.hydrate placement. Top-level hydrate takes
precedence; prefer metadata so content and runtime policy stay distinct. The updated CLI
rejects unsupported strategy names during check and build.
Good candidates for never
Use it for a stylesheet block, plain content or a footer made of text and native links.
CSS and native anchor navigation work without hydration. useStore() or t() can read
server-supplied content without requiring a browser effect of their own.
Do not use never for OrderForm, product variant/offer/add-on pickers, catalogue controls,
interactive galleries, cart state, event handlers, timers or browser effects. The CLI warns
about some known controls, but aliases and external wrappers require review.
A server-only wrapper can contain separate interactive child islands. Under a provider, an explicitly static child remains an HTML slot. Review nested components rather than assuming a parent's policy removes all child JavaScript or preserves a browser provider's behavior.
Keep server and browser output aligned
The first render runs in an isolated environment without window, document, storage or
network access. Read public page seeds synchronously. Put browser work in an effect or event
handler and keep hydration enabled for that component.
For a block with browser-only initialization, prefer a stable initial state rendered on
both sides, then update it in an effect. A typeof window guard can prevent a server throw,
but returning different initial markup on each side can still cause a hydration mismatch.
Do not hide the LCP image until an effect runs. Use image dimensions, responsive sources and visible server HTML; scheduling alone does not fix layout shift.
Review and verify
- Search for handlers, effects, observers, timers, shopper state and interactive scope controls.
- Review slots and provider dependencies, including imported wrappers and aliases.
- Set
neveronly when the block can keep working as HTML and CSS. - Run
npm run check,npm run build,npm run check:perfandnpm test. - In
npm run dev, test the first touch, keyboard focus, navigation and shopping interactions. - Compare browser JavaScript and main-thread work under the same device/network profile.
Inspect the emitted component in dist/data.json to confirm the strategy survived the build.
A passing smoke check does not test hydration. Origin requests/s and RSS are separate from
browser performance; see Helium's benchmark and the
theme performance guide.