Skip to content

Regions

Lakes

Static HTML inside an island. A frozen region that ships zero client JS, although it lives in an interactive tree.

On this page

A lake is static HTML inside an island. The island around it is interactive. The lake itself never ships JS. You mark it with wake: 'none', inside the .svelte file of an island.

When to use

Use a lake for a large static subtree inside an interactive component: prose, a prerendered chart, a highlighted code block, an image gallery. The lake keeps that subtree out of the client bundle of the island. If the static content is not inside an island, it costs nothing already. Unmarked markup on a Page ships no JS. Lakes matter only inside a tree that hydrates.

<!-- inside an island -->
<script>
  import HeavyChart from './HeavyChart.svelte' with { wake: 'none' };
</script>

<section>
  <LiveControls />        <!-- interactive -->
  <HeavyChart data={rows} /> <!-- frozen: rendered once on the server, never hydrated -->
</section>

The lake renders on the server. When the island around it hydrates, ogygia lifts the DOM of the lake out, keeps it safe, and puts it back in place. Thus a costly static subtree stays free of JS inside a live component.

Why not render it inline?

Inside an island that hydrates, ordinary markup compiles into the client bundle of the island. A lake removes a subtree from that bundle. Use it for content that is costly to ship but static: prose, a large prerendered chart, a code block with syntax colors.

The island below is interactive: the counter counts. The snapshot inside it is a lake: server HTML, lifted out and restored around the hydration of the island. It ships no JS of its own.

Island · hydrated
0

The snapshot below is a lake — frozen, ships no JS.

Static snapshot
42

wake: 'none' · lifted & restored

Keeping a lake fresh: render: live

By default, a lake is baked one time and restored unchanged. When its content must refresh in the background, make it render: 'live'. A live lake serves baked static content, then revalidates:

<script>
  import HeavyChart from './HeavyChart.svelte' with { render: 'live', wake: 'visible' };
</script>

<HeavyChart data={rows} />

wake is the revalidate schedule (load / idle / visible / media). maxAge and onExpire, set in a preset, control staleness. render: 'live' serves the baked content immediately. It refreshes the content in the background through the signed island endpoint.

After an SPA navigation, a lake restored inside kept layout chrome is marked as settled again. Thus an island nested in that lake can wake. See SPA router.

A lake on a Kit page (csr = true)

A lake also works at the top of a page, as site chrome, and it survives a page that Kit hydrates. Mark the header import in a layout with wake: 'none'. On a csr = false page the header is plain server HTML. On a csr = true page Kit hydrates the document, but it adopts the lake: the server element stays as it is, byte for byte, and the code of the header ships in no bundle, not even the bundle of Kit.

<script>
  import Header from '$lib/Header.svelte' with { wake: 'none' };
  let { children } = $props();
</script>

<Header />
{@render children()}

Everything inside the lake stays in the world of ogygia on both kinds of page. A server island inside the header (the account menu, the cart count) still fetches. A wake: 'interaction' search box inside it still wakes on the first click. Kit never hydrates the inside of a lake, so the runtime of ogygia does.

That is the shape of a static site header: static markup for the menu, holes for the personal parts, one small island for the search. Anonymous visitors download none of the header JS, and the same header keeps working on the few pages that need csr = true.

A lake needs server HTML

A lake that Kit mounts fresh during a client-side navigation has no server HTML to adopt. It renders empty, and dev mode warns. Keep chrome lakes in a layout that persists across navigations, or serve that route with csr = false.

Do / don’t

  • Do put your heaviest static content (long prose, large charts, code) in a lake when it is inside an island.
  • Do use render: 'live' when the server HTML of a lake must refresh in the background.
  • Don’t put interactive UI in a lake. A lake ships no JS. Buttons and inputs in it are inert. Use a nested client island instead.