Regions
Timing & nesting
The schedules that decide when JS runs or HTML arrives, and the one rule that governs islands inside islands.
On this page
Two small systems cover a lot of ground: when a region wakes, and what happens when regions nest. Both are deliberately tiny.
The schedules
One key, wake, carries the schedule for both islands (wake) and server islands (render: 'deferred'), so you learn the words once:
| Value | When it fires | Good for |
|---|---|---|
load | Immediately after the runtime boots | Above-the-fold, interactive-now UI |
idle | On requestIdleCallback | Nice-to-have widgets that can wait |
visible | When scrolled into the viewport | Anything below the fold |
interaction | On the first pointer / key / focus inside it (islands only) | Anything most visitors never touch |
'(media query)' | When the CSS media query matches | Mobile-only or desktop-only UI |
On an island (render defaults to static) wake says when JS runs. On a server island (render: 'deferred') wake says when the HTML is fetched. Same words either way. (interaction is islands-only: fetching HTML on interaction would mean interacting with a fallback.)
<script>
import Now from '$lib/Now.svelte' with { wake: 'load' };
import Later from '$lib/Later.svelte' with { wake: 'idle' };
import Below from '$lib/Below.svelte' with { wake: 'visible' };
import Phone from '$lib/Phone.svelte' with { wake: '(max-width: 600px)' };
</script>Each of the four below is a real island using exactly that schedule:
Live since —
Idle after …
--:--:--
In view · —
0px · no match
interaction: JS only for what people actually use
wake: 'interaction' is the laziest schedule: the island ships zero JS until someone uses
it. A pointer press, a keystroke, or focus landing inside the region wakes it. The
interaction that woke it is not lost:
- The first click counts. It is captured while the island wakes and replayed the moment it is live. A button, checkbox, or link activates exactly once.
- Typing survives. Characters land in the field natively (it is real HTML); after hydration
the typed value, selection, and focus are restored and
bind:syncs up. - Hover warms it. The pointer entering the region prefetches the chunk, so by the time the press lands the wake is usually instant.
One caveat: the replayed first event is not a trusted browser gesture, so gesture-gated
APIs (window.open, clipboard, fullscreen) will be blocked if called from it. A component can
detect this and adapt:
<script>
import { hydratedBy } from 'ogygia';
// 'load' | 'idle' | 'visible' | 'interaction' | a media query — or null during SSR.
// Call during setup, like getContext.
const woke = hydratedBy();
</script>
{#if woke === 'interaction'}
<!-- first activation was a replay: render a real <a target="_blank"> instead of window.open -->
{/if}Don’t put gesture-gated actions behind interaction; everything else (counters, carts, menus,
forms, editors) just works, and costs nothing until it is touched.
Tuning visible
visible uses an IntersectionObserver. Widen its trigger margin per-import, or set a default for the whole app:
import Chart from '$lib/Chart.svelte' with { wake: 'visible', margin: '200px' };// default margin for every `visible` island
ogygia({ regions: { visible: { margin: '0px' } } });Deferred content that is also interactive
A render: 'deferred' region is content only: it never ships JS. On it, wake is the fetch schedule (below, the hole waits until it scrolls into view before it even costs a request):
import Panel from '$lib/Panel.svelte' with { render: 'deferred', wake: 'visible' };When a fetched hole also needs interactivity, you do not stack two schedules on one import. You nest a normal wake island inside the server island’s own component. render decides how the HTML arrives; wake on the inner island decides when its JS runs. See Server islands → nesting an island.
Nesting: one rule
Walk up the tree. The closest marked parent decides.
| Nesting | What happens |
|---|---|
| Island on the page | Gets its own JS |
| Island inside an island | Shares the parent’s JS: one interactive tree, not two |
| Lake inside an island | Stays static HTML; the lake’s JS never ships |
| Island inside a lake | Gets its own JS again |
| Server island inside an island | Renders inline with the parent (render: 'deferred' ignored) |
page → island → lake → island → …Because an island-in-an-island shares one tree, a child island’s schedule is ignored: it hydrates with its parent. Dev warns when it drops a redundant strategy.
Do / don’t
- Do default below-the-fold islands to
visible. It is free performance. - Do use
idlefor anything the user will not touch immediately. - Don’t try to make a
render: 'deferred'region interactive on its own. It ships no JS. Nest awakeisland inside it instead.