NNicheStackTutorials
← All tutorials
SvelteKit

How I Cut 140KB of Unused JS From My SvelteKit Bundle

rollup-plugin-visualizer findings, dynamic imports for heavy libs, and removing hydration on pages that don't need it.

·11 min read·SvelteKit
How I Cut 140KB of Unused JS From My SvelteKit Bundle cover

Tested on: SvelteKit 2.16, Vite 6 Before: 218 KB JS transferred. After: 74 KB. TBT: 310ms → 45ms.

Step 1 — see what's actually in the bundle

npm install -D rollup-plugin-visualizer

vite.config.ts:

import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    sveltekit(),
    visualizer({ open: true, filename: 'stats.html', gzipSize: true })
  ]
});

Run npm run build. Open stats.html. My top offenders:

  • highlight.js — 89 KB (used on one code-block page)
  • date-fns — 34 KB (I only used format())
  • Svelte runtime on a static about page — 41 KB

Fix highlight.js — dynamic import

Before (bad):

import hljs from 'highlight.js';
hljs.highlightAll();

After:

<script lang="ts">
  import { onMount } from 'svelte';
  let { code, lang } = $props();

  onMount(async () => {
    const hljs = (await import('highlight.js/lib/core')).default;
    const langMod = await import(`highlight.js/lib/languages/${lang}`);
    hljs.registerLanguage(lang, langMod.default);
    // highlight single block only
  });
</script>

Fix date-fns — stop importing the barrel

// BAD — pulls 600+ functions
import { format } from 'date-fns';

// GOOD
import format from 'date-fns/format/index.js';

Kill hydration on static pages

src/routes/about/+page.ts:

export const prerender = true;
export const csr = false;

csr = false means zero client JS for that route. About page dropped from 41 KB to 0 KB JS.

Error I hit with csr: false

Cannot use `onMount` in a component rendered with csr: false

A shared <Header> had onMount for a mobile menu. Split it:

  • Header.svelte — static markup only
  • MobileMenu.svelte — loaded with {#await import('./MobileMenu.svelte')} only on pages with csr: true

Verify

npm run build
# Check .svelte-kit/output/client/_app/immutable/chunks/
ls -la build/_app/immutable/chunks/ | wc -l

Numbers

First Load JS (home): 218 KB → 74 KB
highlight.js chunk: lazy-loaded, not in initial bundle
date-fns: 34 KB → 2.1 KB
about page JS: 41 KB → 0 KB

Rule: if a page doesn't need interactivity, set csr: false. If a lib is heavy and rare, dynamic import it.

Tags: SvelteKitBundle SizePerformanceTree Shaking