Ogygia – SSR Islands for SvelteKit

khromov1 pts0 comments

ogygia — SSR islands for SvelteKit

ogygia

Docs

Playground

Features#<br>We’ve got them.<br>SvelteKit native<br>No Kit patches<br>SSR islands<br>~7.6 KB runtime<br>load · idle · visible<br>Media queries<br>Server islands<br>Lakes<br>Remote functions<br>Async Svelte<br>Form actions<br>SPA router<br>View Transitions<br>Link prefetch<br>Persist layout<br>devalue props<br>Import attributes<br>Named presets<br>Nesting<br>Prerender<br>Snippets<br>csr=false HMR

What it does#<br>SvelteKit’s default is to run client JS for the whole route. That is a strong fit for<br>app-like pages. ogygia is for when you want the opposite authoring default: keep the<br>page as server HTML, and opt individual components into JS.<br>Set csr = false so there is no Kit client bootstrap. What still loads is<br>ogygia’s own runtime: a custom element that wakes islands, plus an optional SPA router —<br>about 7.6 KB min+brotli together. Mark a component import with hydrate, defer, or a preset and it becomes an island : serialized props, its own client chunk, and a schedule for when<br>JS arrives. Everything else stays server HTML.<br>The library does not patch Kit. It is a Vite plugin plus that small runtime and a server<br>handle. Runtime deps are devalue, magic-string, and estree-walker. Peers are Svelte 5.40+, Kit 2.70+, and Vite 5 through 8.<br>Kit is deep-imported for a few internals (remote wire codec, client remote entry), so<br>treat the Kit range as tested rather than a soft semver promise.

route.html · SSR + ~7.6 KB runtime Counter.svelte<br>Search.svelte

The words#<br>WordMeaningYou writePageSSR HTML. No Kit client — tiny ogygia runtime (~7.6 KB).csr = falseIslandBecomes interactivehydrate: 'load' (or idle/visible/media)LakeStatic HTML inside an islandhydrate: 'none'Server islandHTML loaded laterdefer: 'load' (or idle/visible/media)<br>Nesting: island inside island shares the parent's JS. Lake freezes a subtree. Island inside a<br>lake becomes interactive again.

Install#<br>Install the package, register the Vite plugin before sveltekit(), and add the server handle. Then convert routes with csr = false — see Adoption for rolling that out<br>without breaking existing Kit pages.

pnpm add ogygia plugins: [ogygia(), sveltekit()] order matters

vite.config.ts#<br>ogygia() must run before sveltekit() (it also sets enforce: 'pre'). In monorepos it adds its package root to Vite's server.fs.allow so absolute shim/runtime resolves are not blocked outside<br>the app directory. For every option, see Plugin config.

import { sveltekit } from '@sveltejs/kit/vite';<br>import { ogygia } from 'ogygia/vite';<br>import { defineConfig } from 'vite';

export default defineConfig({<br>plugins: [<br>ogygia(), // before sveltekit()<br>sveltekit()<br>});<br>hooks.server.ts#<br>ogygiaHandle() serves the signed island endpoint used by defer and lake remount: 'swr'. Compose it with sequence() if you already have handles. Override the path with ogygiaHandle({ endpoint: '/my-islands' }) if you do not want the<br>default clash-safe emoji route.

// src/hooks.server.ts<br>import { sequence } from '@sveltejs/kit/hooks';<br>import { ogygiaHandle } from 'ogygia/hooks';

export const handle = sequence(ogygiaHandle(), myOtherHandle);

// On each route (or layout) you convert to islands:<br>// src/routes/marketing/+page.ts<br>export const csr = false;

Adoption#<br>Convert one route at a time. Existing Kit pages keep working — including with in the root layout.

ogygia is not an all-or-nothing flip. Wire the plugin and handle once, then opt routes into<br>islands when you are ready. Everything else stays ordinary SvelteKit<br>(csr = true by default).

One route at a time#<br>On a route (or layout group) you want as an islands shell, set export const csr = false and mark the interactive imports with hydrate / defer / preset. Sibling routes with no<br>such export keep Kit’s client bootstrap and hydrate as they do today.<br>A layout-level csr = false applies to every child until a deeper layout or<br>page sets csr = true again — useful when a whole section is ready (docs,<br>marketing) while /app stays Kit.

// src/routes/+layout.svelte — safe on mixed apps<br>script><br>import { OgygiaRouter } from 'ogygia';<br>script>

OgygiaRouter /><br>{@render children()}

// src/routes/blog/+page.ts — convert one route at a time<br>export const csr = false;

// src/routes/blog/+page.svelte<br>script><br>import Comments from '$lib/Comments.svelte' with { hydrate: 'visible' };<br>script>

article>…SSR content…article><br>Comments />

// src/routes/dashboard/+page.ts — leave alone (Kit default)<br>// no `csr = false` → full Kit client, router stays idle<br>Root router without breaking Kit pages#<br>You can render in the outermost layout even while most<br>routes are still Kit pages. The router only intercepts clicks when the document is an<br>islands shell (no Kit bootstrap). On a csr = true page it stays idle — Kit<br>owns navigation.<br>Islands → Kit page: full document load. Kit’s inline bootstrap cannot<br>run after an SPA body swap, so the router hands off on purpose.<br>Islands → islands: SPA body swap (and View Transitions if enabled).<br>Kit → anywhere: Kit’s client router (or a full load), unchanged.<br>Page...

ogygia islands sveltekit server import page

Related Articles