SvelteKit Port: 32.50 kB Gzip, +72% Over Plain Svelte — Meta-Framework Tax, Round Two

typescript dev.to

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.ts load functions
  • 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
Enter fullscreen mode Exit fullscreen mode

Most of the extra bytes are SvelteKit's client runtime:

  1. $app/navigation router — SPA navigation, link interception, history API wrangling
  2. $app/stores — global page, navigating, updated stores
  3. Data loading hydration — the client-side handler for +page.ts load results
  4. 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 -->
Enter fullscreen mode Exit fullscreen mode

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',
    },
  },
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Meta-frameworks ship ~10-18 kB of fixed cost regardless of app size
  2. Single-page SPAs feel this most, because the fixed cost is the entire overhead
  3. 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.

Next up: Qwik City (027). Qwik's resumability model makes "bundle size" a more complicated thing to measure.

Source: dev.to

arrow_back Back to Tutorials