I was working on a SvelteKit dashboard application and noticed the initial page load was sluggish. After investigating, I found we had over 80 separate chunk files - way too many. Too many small chunks means too many HTTP requests, which kills performance on slower connections.

What was too big Link to heading

The biggest culprits were:

  • Chart.js and its adapters (~150KB) being pulled into multiple routes
  • Lodash utilities scattered across components instead of tree-shaken imports
  • Icon sets loading all icons instead of just the ones we used

My target was to get below 20 chunks and keep the initial JS bundle under 200KB. For a dashboard app with lots of interactions, I think anything under 300KB total is reasonable.

Diagnosing the problem Link to heading

First, check for circular dependencies (these prevent Vite from chunking properly):

npx madge --circular src/

After building, count how many chunks you have:

ls build/client/_app/immutable/chunks | wc -l

To see what’s in the build:

fd 'js$' build/client/_app/immutable/

The chunk names include a hash. To find chunks from a specific commit:

export GIT_COMMIT_HASH=$(git rev-parse --short=8 HEAD)
fd $GIT_COMMIT_HASH build

If you want to see what’s importing what, madge can generate a graph:

npx madge --image graph.svg src/

How I fixed it Link to heading

  1. Consolidated chart imports - Created a single lib/charts.ts that exports configured chart components
  2. Fixed lodash imports - Changed import _ from 'lodash' to import { debounce } from 'lodash-es'
  3. Lazy loaded icons - Used dynamic imports for icon components: const Icon = await import('./icons/MyIcon.svelte')
  4. Manual chunks in Vite config - Forced vendor libraries into a single chunk:
// vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['chart.js', 'date-fns'],
        },
      },
    },
  },
}

Got it down to 18 chunks and ~180KB initial load. The key is consolidating shared code and lazy loading anything that doesn’t need to be in the initial bundle.

Further reading Link to heading