🍡 mochi

SSR framework for Svelte 5 + Bun with islands-based selective hydration

On this page

Lazy hydration with mochi:hydrate:visible

Defer hydration until a component scrolls into the viewport. The component still renders server-side on every request, but its JavaScript and CSS are fetched only when the wrapper intersects the viewport via IntersectionObserver.

<!-- file: src/Page.svelte -->
<HeavyChart mochi:hydrate:visible />

Pass an options object to start loading before the element enters the viewport. rootMargin is forwarded straight to IntersectionObserver:

<HeavyChart mochi:hydrate:visible={{ rootMargin: '200px' }} />

The default rootMargin is '0px' — hydration fires the moment the island’s first child crosses the viewport edge. Once intersection fires the observer disconnects, the component bundle imports, the deferred CSS link is appended to <head>, and Svelte hydrates the existing SSR markup.

Do NOT assume the island is fully styled before hydration; instead, accept that lazy islands flash unstyled until their CSS link loads. Bundle critical above-the-fold styles into the page shell or use mochi:hydrate for anything that must look right pre-hydration.

Do NOT nest mochi:hydrate:visible inside another hydratable island; instead, hoist it to the page level — nested hydration directives are rejected at compile time.

Combining with mochi:defer

Stack mochi:defer mochi:hydrate:visible to defer both rendering and hydration: the placeholder ships with the page, the SSR HTML streams in when the deferred fetch resolves, and the JavaScript loads only after the now-rendered island scrolls into view.

<Comments mochi:defer mochi:hydrate:visible={{ rootMargin: '300px' }} />

See Selective hydration for the eager mochi:hydrate directive and Server islands for mochi:defer on its own.