🍡 mochi
← All demos

Hydration Modes

You can render components in multiple ways: pure SSR, mochi:hydrate, mochi:hydrate:visible (default and with rootMargin), and mochi:defer. Each card in the demo below turns green when it hydrates — click to confirm 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 8:28:13 PM

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

mochi:hydrate

Eager hydration — the island ships and boots immediately when the page loads. Use for above-the-fold UI that needs to be interactive right away.

<HydrationTarget mochi:hydrate /> server-rendered only
rendered at 8:28:13 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 8:28:13 PM

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

mochi:hydrate:visible

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 8:28:13 PM

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

mochi:defer

Server island — the component is not in the initial page. A placeholder ships, then the browser fetches the rendered HTML on demand. Pair with hydrated child components to also hydrate parts of the island after it loads.

Loading from server…
View source on GitHub
<script lang="ts">
  import HydrationTarget from './HydrationTarget.svelte';
  import { files } from './files.ts';

  const sources = await loadSources(files);
</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>mochi:hydrate</h2>
    <p>Eager hydration — the island ships and boots immediately when the page loads. Use for above-the-fold UI that needs to be interactive right away.</p>
  </header>
  <HydrationTarget mochi:hydrate label="<HydrationTarget mochi:hydrate />" />
</section>

<section class="mode">
  <header>
    <h2>mochi:hydrate:visible</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>mochi:hydrate:visible</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>mochi:defer</h2>
    <p>
      Server island — the component is <em>not</em> in the initial page. A placeholder ships, then the browser fetches the rendered HTML on demand. Pair with hydrated child components
      to also hydrate parts of the island 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-family: var(--font-serif);
    font-size: 1.35rem;
    font-weight: 500;
  }

  .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>