🍡 mochi
← All demos

Hydration Modes

The same component rendered four different ways. Watch each card turn green when it hydrates — click its button to confirm the JavaScript has taken over. Scroll to reach the lazy islands.

No directive

Pure SSR — no client JavaScript is shipped for this component. It renders on the server and is inert in the browser. Best for content that never needs interactivity.

<HydrationTarget /> server-rendered only
rendered at 3:47:50 PM

This button won't work since the component is not hydrated!

mochi:hydrate

Eager hydration — the island boots as soon as its bundle arrives, making it interactive immediately. Use this for above-the-fold UI that the user will touch right away.

<HydrationTarget mochi:hydrate /> server-rendered only
rendered at 3:47:50 PM

This button won't work since the component is not hydrated!

mochi:hydrate:visible

Lazy hydration — the bundle and CSS are only fetched once the island scrolls into view (via an IntersectionObserver). The card below stays inert until you scroll it into the viewport.

↓ Scroll down ↓

<HydrationTarget mochi:hydrate:visible /> server-rendered only
rendered at 3:47:50 PM

This button won't work since the component is not hydrated!

mochi:hydrate:visible={{ rootMargin }}

You can pass IntersectionObserver options — here rootMargin: '200px' starts hydration a bit before the island is actually in view.

<HydrationTarget mochi:hydrate:visible={{ rootMargin: '200px' }} /> server-rendered only
rendered at 3:47:50 PM

This button won't work since the component is not hydrated!

mochi:defer

Server island — the component is not rendered with the initial page. Instead, a placeholder ships and the browser fetches the rendered HTML from the server on-demand. Pair with mochi:hydrate to also make it interactive after it loads.

Loading from server…
<script lang="ts">
  import HydrationTarget from './HydrationTarget.svelte';
</script>

<section class="mode">
  <header>
    <h2>No directive</h2>
    <p>Pure SSR — no client JavaScript is shipped for this component. It renders on the server and is inert in the browser. Best for content that never needs interactivity.</p>
  </header>
  <HydrationTarget label="<HydrationTarget />" />
</section>

<section class="mode">
  <header>
    <h2><code>mochi:hydrate</code></h2>
    <p>
      Eager hydration — the island boots as soon as its bundle arrives, making it interactive immediately. Use this for above-the-fold UI that the user will touch right away.
    </p>
  </header>
  <HydrationTarget mochi:hydrate label="<HydrationTarget mochi:hydrate />" />
</section>

<section class="mode">
  <header>
    <h2><code>mochi:hydrate:visible</code></h2>
    <p>
      Lazy hydration — the bundle and CSS are only fetched once the island scrolls into view (via an <code>IntersectionObserver</code>). The card below stays inert until you
      scroll it into the viewport.
    </p>
  </header>
  <div class="spacer">
    <p>↓ Scroll down ↓</p>
  </div>
  <HydrationTarget mochi:hydrate:visible label="<HydrationTarget mochi:hydrate:visible />" />
</section>

<section class="mode">
  <header>
    <h2><code>mochi:hydrate:visible={'{{ rootMargin }}'}</code></h2>
    <p>
      You can pass <code>IntersectionObserver</code> options — here
      <code>rootMargin: '200px'</code>
      starts hydration a bit before the island is actually in view.
    </p>
  </header>
  <HydrationTarget mochi:hydrate:visible={{ rootMargin: '200px' }} label={`<HydrationTarget mochi:hydrate:visible={{ rootMargin: '200px' }} />`} />
</section>

<section class="mode">
  <header>
    <h2><code>mochi:defer</code></h2>
    <p>
      Server island — the component is <em>not</em> rendered with the initial page. Instead, a placeholder ships and the browser fetches the rendered HTML from the server
      on-demand. Pair with <code>mochi:hydrate</code> to also make it interactive after it loads.
    </p>
  </header>
  <HydrationTarget mochi:defer mochi:hydrate label="<HydrationTarget mochi:defer mochi:hydrate />">
    <div class="placeholder">Loading from server…</div>
  </HydrationTarget>
</section>
Styles
<style>
  .mode {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin-bottom: 2.5rem;
  }

  .mode header {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
  }

  .mode h2 {
    margin: 0;
    font-size: 1.05rem;
  }

  .mode h2 code,
  .mode p code {
    font-family: var(--font-mono);
    background: var(--surface-muted);
    border: 1px solid var(--border);
    color: var(--text);
    padding: 0.1em 0.35em;
    border-radius: 4px;
    font-size: 0.85em;
    font-weight: 500;
  }

  .mode p {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.95rem;
    line-height: 1.5;
  }

  .spacer {
    height: 60vh;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px dashed var(--border);
    border-radius: var(--radius-md);
    color: var(--text-subtle);
    font-size: 0.9rem;
  }

  .spacer p {
    margin: 0;
  }

  .placeholder {
    padding: 1rem;
    border: 2px dashed var(--border-strong);
    border-radius: var(--radius-md);
    color: var(--text-subtle);
    text-align: center;
    font-size: 0.9rem;
  }
</style>