Island Props
The parent has no mochi:hydrate directive, so its own code never ships to the browser — only the child island does. Watch how the props (a Date, a Map, a Set) cross the SSR → client boundary intact.
ServerRenderedParent.svelte
Runs only on the server. No mochi:hydrate directive, so this component's own code ships zero JavaScript — its output is inert HTML. It builds a props bag with mixed
types and hands it off to the child below.
const user = { name: 'Ada', id: 42 };
const visitedAt = new Date();
const tags = new Set(['svelte', 'bun', 'islands']);
const scores = new Map([['speed', 95], ['dx', 88], ['size', 72]]);| Prop | Value | Runtime type |
|---|---|---|
user | { name: "Ada", id: 42 } | Object |
visitedAt | 2026-05-05T15:50:44.096Z | Date |
tags | Set(3) { svelte, bun, islands } | Set |
scores | Map(3) { speed => 95, dx => 88, size => 72 } | Map |
How the props get there
- At SSR time, Mochi calls
devalue.stringifyon the props you pass to the island. UnlikeJSON.stringify, it preservesDate,Map,Set,BigInt,RegExp,URL, typed arrays,undefined,NaN, and even cyclic references. - The serialized string is placed on the
<mochi-hydratable-island>custom element as apropsattribute. (When two islands share the exact same payload, it gets hoisted into a shared<script type="application/json">block and the attribute becomes aprops-refpointer instead.) - In the browser, the custom element's
connectedCallbackreads that string and runsdevalue.parseon it, reconstructing the rich types. - The result is handed to Svelte's
hydrate(...)as the component's props — and the child takes over.
Try it: view source on this page (or inspect the mochi-hydratable-island element) and you'll see the serialized props on the props attribute. Functions, class instances, and Symbols can't be serialized — pass a plain-data representation, or compute them after hydration.
<script lang="ts">
import ClientRenderedChild from './ClientRenderedChild.svelte';
const user = { name: 'Ada', id: 42 };
const visitedAt = new Date();
const tags = new Set(['svelte', 'bun', 'islands']);
const scores = new Map<string, number>([
['speed', 95],
['dx', 88],
['size', 72],
]);
</script>
<section class="parent">
<header>
<h2>ServerRenderedParent.svelte</h2>
<p>
Runs only on the server. No <code>mochi:hydrate</code> directive, so this component's own code ships zero JavaScript — its output is inert HTML. It builds a props bag with mixed
types and hands it off to the child below.
</p>
</header>
<pre><code
>{`const user = { name: 'Ada', id: 42 };
const visitedAt = new Date();
const tags = new Set(['svelte', 'bun', 'islands']);
const scores = new Map([['speed', 95], ['dx', 88], ['size', 72]]);`}</code
></pre>
</section>
<div class="arrow" aria-hidden="true">
<span>props</span>
<span class="line"></span>
<span class="head">▼</span>
</div>
<ClientRenderedChild {user} {visitedAt} {tags} {scores} mochi:hydrate />
<section class="explainer">
<h3>How the props get there</h3>
<ol>
<li>
At SSR time, Mochi calls <a href="https://github.com/Rich-Harris/devalue" target="_blank" rel="noreferrer"><code>devalue.stringify</code></a>
on the props you pass to the island. Unlike <code>JSON.stringify</code>, it preserves
<code>Date</code>, <code>Map</code>, <code>Set</code>, <code>BigInt</code>,
<code>RegExp</code>, <code>URL</code>, typed arrays, <code>undefined</code>,
<code>NaN</code>, and even cyclic references.
</li>
<li>
The serialized string is placed on the <code><mochi-hydratable-island></code> custom element as a <code>props</code> attribute. (When two islands share the exact same
payload, it gets hoisted into a shared <code><script type="application/json"></code> block and the attribute becomes a <code>props-ref</code> pointer instead.)
</li>
<li>
In the browser, the custom element's <code>connectedCallback</code> reads that string and runs
<code>devalue.parse</code> on it, reconstructing the rich types.
</li>
<li>
The result is handed to Svelte's <code>hydrate(...)</code> as the component's props — and the child takes over.
</li>
</ol>
<p class="hint">
<strong>Try it:</strong> view source on this page (or inspect the
<code>mochi-hydratable-island</code>
element) and you'll see the serialized props on the
<code>props</code> attribute. Functions, class instances, and Symbols can't be serialized — pass a plain-data representation, or compute them after hydration.
</p>
</section>
Styles
<style>
.parent {
border: 2px dashed var(--border);
border-radius: var(--radius-md);
padding: 1rem 1.25rem;
background: var(--surface);
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.parent header {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.parent h2 {
margin: 0;
font-family: var(--font-mono);
font-size: 1rem;
font-weight: 600;
color: var(--text);
}
.parent p {
margin: 0;
color: var(--text-muted);
font-size: 0.95rem;
line-height: 1.5;
}
.parent 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;
}
pre {
margin: 0;
padding: 0.75rem 0.85rem;
background: var(--surface-muted);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
overflow-x: auto;
font-size: 0.85rem;
line-height: 1.5;
}
pre code {
background: transparent;
border: none;
padding: 0;
font-family: var(--font-mono);
color: var(--text);
}
.arrow {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.15rem;
color: var(--text-subtle);
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.08em;
margin: 0.4rem 0;
}
.arrow .line {
width: 2px;
height: 1.25rem;
background: var(--border-strong);
}
.arrow .head {
color: var(--border-strong);
line-height: 1;
}
.explainer {
margin-top: 1.5rem;
padding: 1rem 1.25rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
color: var(--text);
}
.explainer h3 {
margin: 0 0 0.6rem 0;
font-family: var(--font-serif);
font-size: 1.1rem;
font-weight: 500;
}
.explainer ol {
margin: 0;
padding-left: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
font-size: 0.95rem;
line-height: 1.55;
color: var(--text-muted);
}
.explainer 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;
}
.explainer a {
color: var(--accent);
}
.hint {
margin: 0.85rem 0 0 0;
padding: 0.75rem 0.85rem;
background: var(--surface-muted);
border-left: 3px solid var(--accent);
border-radius: var(--radius-sm);
font-size: 0.92rem;
line-height: 1.55;
color: var(--text-muted);
}
.hint strong {
color: var(--text);
}
</style>