Regions
Lakes
Static HTML inside an island. A lake is a frozen region that ships no client JavaScript even though it sits in an interactive tree.
On this page
Static markup on a csr = false page already costs nothing, because unmarked markup ships no JavaScript. The cost appears once that markup sits inside an island: everything in a hydrating component compiles into the island’s client bundle, including long prose, a prerendered chart, a highlighted code block or an image gallery that will never change. A lake is static HTML inside an island that stays out of that bundle. The island around it is interactive, but the lake itself never ships JavaScript. You mark it with wake: 'none' inside the island’s .svelte file.
<!-- 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 lake’s DOM out, keeps it aside and puts it back in place afterwards, so an expensive static subtree adds no JavaScript to a live component.
Why not render it inline?
Inside a hydrating island, ordinary markup compiles into the island’s client bundle, and a lake is how you take a subtree back out of it. That matters most for content that is expensive to ship but never changes, such as prose, a large prerendered chart or a syntax-highlighted code block.
The island below is interactive, and its counter counts. The snapshot inside it is a lake: server HTML that is lifted out and restored around the island’s hydration, with no JavaScript of its own.
Keeping a lake fresh: render: live
By default, a lake is baked once and restored unchanged. When its content has to refresh in the background, make it render: 'live'. A live lake serves its baked static content first and then revalidates:
<script>
import HeavyChart from './HeavyChart.svelte' with { render: 'live', wake: 'visible' };
</script>
<HeavyChart data={rows} />On a live lake, wake is the revalidation schedule (load, idle, visible or a media query), and maxAge and onExpire, set in a preset, control how stale it may get. The baked content shows immediately, and the refresh happens in the background through the signed island endpoint.
After an SPA navigation, a lake restored inside kept layout chrome is marked as settled again, so 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 on 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 adopts the lake, so the server element stays exactly as it was, byte for byte, and the header’s code ships in no bundle, not even Kit’s.
<script>
import Header from '$lib/Header.svelte' with { wake: 'none' };
let { children } = $props();
</script>
<Header />
{@render children()}Everything inside the lake stays in ogygia’s world on both kinds of page. A server island inside the header, such as the account menu or the cart count, still fetches, and a wake: 'interaction' search box inside it still wakes on the first click. Kit never hydrates the inside of a lake, so ogygia’s runtime does.
This gives you the usual shape of a static site header: static markup for the menu, holes for the personal parts and one small island for the search. Anonymous visitors download none of the header’s JavaScript, 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, so 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 sits inside an island.
- Do use
render: 'live'when a lake’s server HTML has to refresh in the background. - Don’t put interactive UI in a lake. A lake ships no JavaScript, so buttons and inputs in it are inert; use a nested client island instead.