.dev
Storefront templates

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>;
}
StrategyBehavior
OmittedDefault scheduling: chrome/top block uses idle; remaining cart/checkout content uses load; other islands use visible
loadBegin hydration immediately
idleHydrate at browser idle time; pointer activity can wake it sooner
visibleHydrate near the viewport; pointer activity or a timeout also wakes it
interactionHydrate on pointer, focus or keyboard activity, with an idle fallback
neverKeep 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

  1. Search for handlers, effects, observers, timers, shopper state and interactive scope controls.
  2. Review slots and provider dependencies, including imported wrappers and aliases.
  3. Set never only when the block can keep working as HTML and CSS.
  4. Run npm run check, npm run build, npm run check:perf and npm test.
  5. In npm run dev, test the first touch, keyboard focus, navigation and shopping interactions.
  6. 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.

On this page