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
- Consolidated chart imports - Created a single
lib/charts.tsthat exports configured chart components - Fixed lodash imports - Changed
import _ from 'lodash'toimport { debounce } from 'lodash-es' - Lazy loaded icons - Used dynamic imports for icon components:
const Icon = await import('./icons/MyIcon.svelte') - 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.