SvelteKit Port: 32.50 kB Gzip, +72% Over Plain Svelte — Meta-Framework Tax, Round Two
Plain Svelte 5 shipped at 18.92 kB. SvelteKit on the exact same spec ships at 32.50 kB — a +72% regression for adding a meta-framework over the same core. The consolation: at 32.50 kB it's still −33% smaller than React. Meta-framework tax is real across ecosystems; Svelte's base is just small enough to absorb it.
Entry #6 in the framework comparison series. The previous entry on Nuxt established the "meta-framework tax" pattern — Vue 28.76 kB → Nuxt 52.01 kB, a +81% jump. SvelteKit shows the same pattern: Svelte 18.92 kB → SvelteKit 32.50 kB, +72%. Different ecosystem, same phenomenon.
🔗 Live demo: https://sen.ltd/portfolio/portfolio-app-sveltekit/
📦 GitHub: https://github.com/sen-ltd/portfolio-app-sveltekit
Configuration: SvelteKit with @sveltejs/adapter-static, pure SPA, zero SSR. This is an apples-to-apples byte comparison, not a SvelteKit feature evaluation.
SvelteKit vs. Svelte — the structural relationship
Svelte is a component framework. SvelteKit is a meta-framework built on top, offering:
- File-based routing (
src/routes/) - SSR / SSG / SPA mode switching
- Nested layouts
- Data loading via
+page.tsloadfunctions - Form actions and enhancement
This is the same relationship React has to Next, Vue has to Nuxt. You can use the base framework standalone; the meta-framework gives you conventions for everything around the components.
Where the +13.58 kB went
.svelte-kit/output/client/_app/immutable/entry/start.<hash>.js gzip 32.50 kB
Most of the extra bytes are SvelteKit's client runtime:
-
$app/navigationrouter — SPA navigation, link interception, history API wrangling -
$app/stores— globalpage,navigating,updatedstores -
Data loading hydration — the client-side handler for
+page.tsloadresults - Prefetch scheduling — hover/focus-driven preload hints
Structurally identical to Nuxt's story. The framework ships the machinery for features this specific app doesn't use, because those features are central to what SvelteKit is.
Routing for an app with one route
The app is one page (src/routes/+page.svelte). There's no navigation to be had. But SvelteKit's client-side router is baked in at the top of the app entrypoint and can't be tree-shaken, because the codebase assumes it exists. Every app pays +10 kB of router cost regardless of route count.
This isn't a bug — it's an architectural commitment. SvelteKit is designed for apps that grow, and routing is the primary thing they grow along. Paying the fixed cost for a single-page app is like renting a highway for one car: inefficient unless you expect more traffic soon.
But SvelteKit is still smaller than React
The saving grace is that Svelte's base framework is small enough that even with meta-framework overhead, SvelteKit stays below React:
| Port | gzip |
|---|---|
| Svelte | 18.92 kB |
| SvelteKit | 32.50 kB |
| React | 49.00 kB |
| Nuxt | 52.01 kB |
SvelteKit at 32.50 kB is −33% smaller than React at 49.00 kB. The "meta-framework tax" gets absorbed because Svelte's baseline is so low.
Compare to Nuxt's situation: Vue baseline of 28.76 kB is already smaller than React, but adding Nuxt pushes it past React (52.01 kB vs 49.00 kB). The base framework's starting point determines whether meta-framework overhead pushes you into "heavier than the competition" territory.
Meta-framework tax is survivable if your base is lean enough. That's the SvelteKit vs Nuxt lesson in one sentence.
Code reads nearly identical to plain Svelte
Moving the component into SvelteKit's routes directory is almost the entire port:
<!-- src/routes/+page.svelte -->
<script lang="ts">
import type { PortfolioData, Lang } from '$lib/types'
import { loadPortfolioData } from '$lib/data'
import { filterAndSort, type FilterState } from '$lib/filter'
import { MESSAGES, detectDefaultLang } from '$lib/i18n'
let status = $state<'loading' | 'error' | 'ready'>('loading')
let data = $state<PortfolioData | null>(null)
let lang = $state<Lang>(detectDefaultLang())
// ...
$effect(() => {
loadPortfolioData()
.then((d) => { data = d; status = 'ready' })
.catch((e) => { status = 'error' })
})
</script>
<!-- template markup -->
Changes from the plain Svelte port: file renamed from App.svelte to +page.svelte, imports use the $lib alias. The component logic and Runes usage are identical. The +13 kB all comes from outside your components, in the framework code that loads them.
adapter-static configuration
Deploying to a subpath on sen.ltd needs paths.base:
import adapter from '@sveltejs/adapter-static'
export default {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: 'index.html',
precompress: false,
}),
paths: {
base: '/portfolio/portfolio-app-sveltekit',
},
},
}
The base setting tells SvelteKit to prefix internal asset URLs so the build works when served from a subdirectory. This is a nice SvelteKit-specific convenience — doing this manually in plain Svelte would mean rewriting the Vite base plus adjusting a few link paths.
Tests
14 Vitest cases on filter.ts. Same suite as every other port.
Scoreboard
| Port | gzip | vs React |
|---|---|---|
| 021 React | 49.00 kB | — |
| 022 Vue | 28.76 kB | −41% |
| 023 Svelte | 18.92 kB | −61% |
| 024 Solid | 8.33 kB | −83% |
| 025 Nuxt | 52.01 kB | +7% |
| 026 SvelteKit | 32.50 kB | −33% |
Meta-framework tax: the rule
Two data points now (Nuxt and SvelteKit), with consistent patterns:
- Meta-frameworks ship ~10-18 kB of fixed cost regardless of app size
- Single-page SPAs feel this most, because the fixed cost is the entire overhead
- Base framework size determines "worth it" threshold — a lighter base absorbs meta overhead better
The right decision rule: if your app has one to three pages, use the base framework directly. If it has ten or more pages, or needs SSR / data loading / layouts, the meta-framework pays for itself because you'd otherwise reinvent those features yourself at comparable cost and worse quality.
This sounds obvious in hindsight, but I see SPAs using Next or Nuxt all the time for single-page apps and wondering why their Lighthouse scores are low. The answer is usually "you're paying tax for features you're not using."
Series
This is entry #26 in my 100+ public portfolio series, and #6 in the framework comparison.
- 📦 Repo: https://github.com/sen-ltd/portfolio-app-sveltekit
- 🌐 Live: https://sen.ltd/portfolio/portfolio-app-sveltekit/
- 🏢 Company: https://sen.ltd/
Next up: Qwik City (027). Qwik's resumability model makes "bundle size" a more complicated thing to measure.