.dev

Give every block a browser budget

Why Helium keeps custom components interactive by default, and how theme authors can remove browser work with confidence.

FeeefFeeef engineering

A footer can look finished before any JavaScript runs. A product selector can look finished while doing nothing. Server-rendered HTML makes those two cases easy to confuse.

Helium's hydration contract gives theme authors a direct way to distinguish them. Custom components hydrate by default. When a block is verified to be static, its metadata can say hydrate: "never". That small declaration is a decision about behavior, not just speed.

Why we don't guess that a component is static

Source detection is useful for hints. It can spot a familiar hook or an order form. It cannot prove a component needs no browser work. A hook can be aliased; an imported wrapper can own state; an effect can start an observer without rendering a button.

If “we didn't recognize a hook” means “skip hydration,” a perfectly good theme can ship controls that look right and never respond. We chose an interactive default so authors have to opt into that tradeoff explicitly.

This is especially important in a theme system. The original author knows what a component does, but a merchant may later place it in a different slot or combine it with other blocks. The contract needs to survive that editing.

Start with the blocks that only print HTML

A simple delivery note is a good candidate:

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 className="delivery-note">{props.message}</p>;
}

The merchant can still edit the message. The server still prints it. The browser does not attach this component's handlers or run its effects. Plain links and CSS continue to work because they are browser features already.

This is the change we made to the Helium starter and the Nour, Souk and Rimal packs: the stylesheet block and simple footer explicitly stay as server HTML. We left interactive headers and shopping controls on their normal path.

Timing is a different choice from removal

For blocks that need browser behavior, Helium supports load, idle, visible and interaction. The default scheduler gives chrome idle priority, cart and checkout content load priority, and other custom islands visible priority.

An explicit load is useful for a control that must initialize promptly. visible defers work until a block approaches the viewport, with interaction and time-based fallbacks. interaction wakes on pointer, focus or keyboard activity, with an idle fallback. It is not a promise that code will stay unloaded forever until a click.

Scheduling can reduce competition during the first paint. It does not remove the component's code or fix expensive effects. Keep the component small and test the first interaction, including keyboard focus and touch.

Review behavior before removing hydration

A short review catches more than a search for useState:

  • Does the block attach events or run an effect, timer, observer or analytics callback?
  • Does it use browser APIs during render, or read shopper-specific state?
  • Does it wrap OrderForm, a variant/offer/add-on picker, a gallery or catalogue controls?
  • Do nested blocks depend on a provider from this component?

Keep those components hydrated. Separate child islands can remain interactive inside a static wrapper, but a statically rendered wrapper cannot provide browser effects of its own. Review the actual slot structure and try the interaction in the hosted preview.

The CLI's Helium validation now rejects unsupported strategy names and warns about known interactive components under never. The warning is advisory; an alias can still hide the control from a source check. These checks ship with the updated CLI code and starter pack; use them alongside browser testing.

Measure the right outcome

After changing hydration, run the package checks, build and hosted draft preview. Check clicks, focus, variants, cart totals and translations. Then compare browser JavaScript, main-thread work and Lighthouse on the same pages and device profile.

The origin benchmark measures server work and RSS. It does not establish a browser speedup from these starter changes. Keeping those measurements separate helps us find the next useful improvement.

The practical rule is simple: every interactive block should have a reason to wake up, and every never should have a reason it can stay asleep. The hydration reference has the complete contract; create a Helium theme to try it.

On this page