Skip to content

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:

ValueWhen it firesGood for
loadImmediately after the runtime bootsAbove-the-fold, interactive-now UI
idleOn requestIdleCallbackNice-to-have widgets that can wait
visibleWhen scrolled into the viewportAnything below the fold
interactionOn the first pointer / key / focus inside it (islands only)Anything most visitors never touch
'(media query)'When the CSS media query matchesMobile-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.

NestingWhat happens
Island on the pageGets its own JS
Island inside an islandShares the parent’s JS: one interactive tree, not two
Lake inside an islandStays static HTML; the lake’s JS never ships
Island inside a lakeGets its own JS again
Server island inside an islandRenders 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 idle for 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 a wake island inside it instead.