Skip to content

Reference

Performance

Client JS, Lighthouse, CLS/LCP, and JS eval measured against SvelteKit, Astro and Mochi on identical pages.

On this page

ogygia is an islands library for SvelteKit. Here are the measurements, reproducible in-repo under bench/.

The benchmark

The same blog post (the same prose, the same five interactive widgets, the same CSS) is built four ways. We measure how much client JavaScript each ships as the post gets longer. Islands ship JS only for the widgets. Thus their payload is flat. Whole-page hydration ships JS for the full document. Thus it grows with the content.

The method is a port of the interactive-blogs-benchmark by Stanislav Khromov, with full credit to @khromov. We run it on the latest tools, with ogygia added.

Client JavaScript as the post grows longer — islands stay flat; whole-page hydration climbs.
0125250375500JS KB (uncompressed)9k23k71kwords in postogygiasveltekitastromochi
Client JS (brotli)
ogygia
27 KB
sveltekit
34 KB
astro
10 KB
mochi
17 KB
Lighthouse score
ogygia
92
sveltekit
85
astro
96
mochi
93
LCP (avg)
ogygia
2.6 s
sveltekit
3.6 s
astro
2.3 s
mochi
2.6 s
CLS (avg)
ogygia
0.000
sveltekit
0.000
astro
0.000
mochi
0.000
JS eval (median)
ogygia
17 ms
sveltekit
26 ms
astro
17 ms
mochi
12 ms

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. The whole-page hydration of SvelteKit climbs with the prose.
  • ogygia vs the other islands. All four now ship Svelte 5 islands. Thus this is a pure architecture comparison, with no framework handicap. The island runtime of Astro is the smallest, then Mochi, then 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 that 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.json. Thus 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:

CapabilitySvelteKitAstroMochiogygia
SSR / prerender✅✅✅✅
Partial hydration (islands)❌ whole-page✅✅✅
Client JS flat as content grows❌✅✅✅
Islands are Svelte 5✅✅ (any framework)✅✅
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 the Kit-native integration that 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-size

Numbers on this page are regenerated from bench/results/latest.json: the exact JSON that the benchmark emits.