Lazy Server Islands
Server islands marked with mochi:defer:visible only fetch their HTML when the wrapper scrolls into view, via an IntersectionObserver. Each island is fetched independently from /_mochi/island/... — open the Network panel and scroll to watch the requests fire on demand. The first island uses rootMargin to start fetching 200px before it enters the viewport.
Scroll down to trigger each fetch. The IntersectionObserver fires per-island.
Island #1 — rootMargin: '200px'
Loading server island
Island #2
Loading server island
Island #3
Loading server island
Island #4
Loading server island
Island #5
Loading server island
<script>
import LazyServerDemo from './LazyServerDemo.svelte';
import { files } from './files.ts';
const sources = await loadSources(files);
</script>
<p class="hint">Scroll down to trigger each fetch. The IntersectionObserver fires per-island.</p>
<div class="stack">
<div class="item">
<h3>Island #1 — <code>rootMargin: '200px'</code></h3>
<LazyServerDemo mochi:defer:visible={{ rootMargin: '200px' }} index={1}>
<div class="island-loading">Loading server island<span class="dots"></span></div>
</LazyServerDemo>
</div>
{#each [2, 3, 4, 5] as i (i)}
<div class="spacer"></div>
<div class="item">
<h3>Island #{i}</h3>
<LazyServerDemo mochi:defer:visible index={i}>
<div class="island-loading">Loading server island<span class="dots"></span></div>
</LazyServerDemo>
</div>
{/each}
</div>
Styles
<style>
.hint {
font-size: 0.9rem;
color: var(--text-muted);
margin: 0 0 1rem;
}
.stack {
display: flex;
flex-direction: column;
}
.item h3 {
font-size: 0.95rem;
font-weight: 600;
color: var(--text-muted);
margin: 0 0 0.5rem;
}
.item h3 code {
font-size: 0.85em;
background: var(--surface-muted);
padding: 0.1em 0.4em;
border-radius: 4px;
}
.spacer {
height: 70vh;
}
.island-loading {
padding: 1rem;
border: 2px dashed var(--border-strong);
border-radius: var(--radius-md);
background: var(--surface-muted);
color: var(--text-subtle);
font-style: italic;
text-align: center;
}
.dots::after {
content: '';
display: inline-block;
width: 1.5em;
text-align: left;
animation: dots 1.2s steps(4, end) infinite;
}
@keyframes dots {
0% {
content: '';
}
25% {
content: '.';
}
50% {
content: '..';
}
75% {
content: '...';
}
}
</style>
More demos
Shared State How shared state across islands works — two separate islands driving the same reactive $state. Custom Transitions How custom view transitions work — supply your own @keyframes to <ViewTransitions /> via custom={{ in, out }}. Image: Invalidation How image invalidation works — invalidateImage() hard-evicts the shared original so every named size re-fetches in lockstep.