Reference
Performance
Client JS, Lighthouse, CLS/LCP, and JS eval measured against SvelteKit, Astro and Mochi on identical pages.
ogygia is an islands library for SvelteKit. Here are the measurements, reproducible in-repo under bench/.
The benchmark
The same blog post (same prose, same five interactive widgets, same CSS) is built four ways, and we measure how much client JavaScript each ships as the post gets longer. Islands ship JS only for the widgets, so their payload is flat. Whole-page hydration ships JS for the entire document, so it grows with the content.
The method is a port of Stanislav Khromov’s interactive-blogs-benchmark, with full credit to @khromov. We run it on latest tooling with ogygia added.
Mobile Lighthouse, simulated throttling. Sizes are brotli/raw of all JS the page loads. Method ported from khromov/interactive-blogs-benchmark. Reproduce: pnpm run e2e is separate; see bench/.
Reading it
- Islands stay flat. ogygia, Astro and Mochi ship the same JS whether the post is 9k or 71k words. SvelteKit’s whole-page hydration climbs with the prose.
- ogygia vs the other islands. Astro’s Preact islands are the smallest; Mochi (Svelte 5 islands) is leaner than ogygia. ogygia trades a little more baseline runtime for a much larger feature surface: server islands, held/live/frozen regions, and full SvelteKit integration (see the matrix below).
- The runtime is feature-selected. ogygia ships only the features your app uses; a load-only page
never pays for the router, lakes, or persistence. We track the per-profile runtime size in
e2e/bundle-size.snapshot.jsonso it can only shrink over time.
What you can build
Speed is easy to match with islands; the difference is surface area. Here are the four benchmarked frameworks, by what they let you build:
| Capability | SvelteKit | Astro | Mochi | ogygia |
|---|---|---|---|---|
| SSR / prerender | ✅ | ✅ | ✅ | ✅ |
| Partial hydration (islands) | ❌ whole-page | ✅ | ✅ | ✅ |
| Client JS flat as content grows | ❌ | ✅ | ✅ | ✅ |
| Islands are Svelte 5 | ✅ | ➖ (Preact et al.) | ✅ | ✅ |
Per-component wake: load·idle·visible·interaction·media | ❌ | ➖ (no interaction) | ? | ✅ |
| Server islands (defer render to an endpoint) | ✅ | ✅ | ? | ✅ |
Held regions: region() values you place like data | ❌ | ❌ | ❌ | ✅ |
| Frozen regions (lakes): zero-JS interactive-context subtrees | ❌ | ❌ | ❌ | ✅ |
| Live / streaming regions | ❌ | ❌ | ? | ✅ |
Built on SvelteKit: its adapters, hooks, $app, remote functions | ✅ | ❌ own framework | ❌ own framework | ✅ |
| Opt-in SPA router | ✅ | ➖ (MPA + view transitions) | ? | ✅ |
| Markdown content collections built in | ➖ add mdsvex | ✅ | ✅ | ✅ |
| Feature-selected runtime (ship only what the app uses) | — | — | ❌ | ✅ |
? = not verified for the benchmarked version. The trade-off: ogygia carries a little more baseline runtime (the size table above) for the bottom third of this matrix, the region primitives and Kit-native integration nothing else here has.
Reproduce it
# hermetic (Docker): builds every framework + runs Lighthouse
docker compose -f bench/docker-compose.yml up --build
# isolated runtime size per feature profile (rolldown, brotli)
pnpm run bundle-sizeNumbers on this page are regenerated from bench/results/latest.json, the exact JSON the benchmark
emits.