Performance

Splitting the JavaScript bundle: what actually changes

Our build produces 48 chunks at 172 kB gzipped, and 43 chunks stay under 15 kB. How to split, what it buys and when it goes too far.

Key points

  • Splitting the bundle removes from the first paint the code needed later.
  • Our build with Vite 5.4.21 produces 48 chunks, 511 kB raw and 172 kB gzipped.
  • 43 of 48 chunks stay under 15 kB: route-level splitting works.
  • Under 5 kB per chunk the request costs more than the code it saves.

Splitting the bundle means loading only the first screen's code on startup and fetching the rest when it is needed: in our build that is 48 chunks and 172 kB gzipped in total.

The three cuts that matter

CutWhat it separatesGainCost
RouteOne page per fileA lighter first paintOne round trip on navigation
VendorStable dependenciesA long cache across deploysThe vendor chunk stays large
FeatureHeavy code used after a clickNothing at load timeA loading state to manage

The route cut is the one that moves the numbers: the entry stays small and every page pays for its own code. The vendor cut is about caching, not about the first paint.

Measuring the real chunks

node -e "const fs=require('fs'),z=require('zlib'),d='client/dist/assets';const f=fs.readdirSync(d).filter(x=>x.endsWith('.js'));let g=0;for(const x of f)g+=z.gzipSync(fs.readFileSync(d+'/'+x)).length;console.log(f.length,'chunk',Math.round(g/1024),'kB gzip')"

The command needs Node and a build that already ran. It answers one question only: how many bytes of JavaScript the first page downloads, not how many the whole project holds.

When it goes too far

  • Under 5 kB per chunk the request costs more than the code it saves.
  • One chunk per component multiplies requests and pushes the problem onto the network.
  • Keep route prefetching selective: precaching everything cancels the cut.

The limitation to name

An extra chunk is not free: it costs a request, a header and a parse. HTTP/2 lowers the cost per request, but parse and compile stay on the main thread and mobile users feel them. The number to watch is not the sum of the chunks but the graph the first screen actually loads.

Where to continue

The EneUI catalogue indexes 111,985 components, 1,556 blocks and the record CPU architecture: a chart with hundreds of nodes belongs in its own chunk, not in the entry. For the single import that grows the bundle read one import that grows the bundle, for library weight the weight of a UI library, and for the measured comparison React libraries 2026.

Frequently asked questions

Are code splitting and bundle splitting the same?

In practice yes: both divide the output into several files. Code splitting describes the cut in the source, bundle splitting the output of the bundler.

Do I need a bundler to split a bundle?

Not strictly, but it helps: Vite, Rollup and webpack handle chunk boundaries and hashed names. By hand you are left with dynamic import().

Below what size does splitting stop paying?

Under 5 kB per chunk: the request costs more than the code it avoids, especially on mobile networks.

Does route splitting slow navigation down?

The first navigation pays one round trip. Prefetch the most likely chunk instead of precaching every chunk.

How do I measure the real weight?

With gzip over the dist files and the sum of the chunks the first page really loads. The project total is not the number the user sees.