Bundle Optimization: Splitting Chunks Can Make First Paint Slower—The Real Lever Is Invalidation
A wrong hashing strategy can make code splitting slower than no splitting at all, costing users 2.7x in total load time over a month of revisits. The fix—contenthash filenames and deterministic module ids—is cheap and should be the first step in any bundle budget.
Bundle optimization isn't about shrinking numbers—it's balancing three budgets: initial bytes, request shape, and invalidation surface. The first two live in the build config; the third lives in the filename's hash, and it carries the most leverage. Splitting a 960KB entry into 16 slices costs 60ms on first paint; excluding 1230KB from the critical path saves 1492ms. Route splitting only pays when shared modules are hoisted—duplicating them makes three navigations transfer 671.9KB, more than the 657KB single bundle.
Over twenty revisits, using fullhash makes splitting lose 2.7x—24310ms vs 23876ms for no splitting—while contenthash plus deterministic module ids cuts that to 8981ms. Tree shaking boils down to a sideEffects declaration: omitting it inflates a 4KB import to 194KB. The optimal chunk granularity depends on revisit frequency and release cadence; V=1 wants one chunk, V=20 wants 192. Teams should measure invalidation surface, not total size.
The strongest signal is that invalidation surface, the metric nobody tracks, outweighs both initial bytes and request count; teams measuring only bundle size are optimizing the wrong number.
The model's constants are calibrated estimates, but the ordering of conclusions is robust because it rests on additive costs and structural invariants; the relative trade-offs matter more than the absolute milliseconds.
The sideEffects story is a reminder that tree shaking is a contract between package authors and bundlers; omitting one field costs 46x in bytes, which is more than any splitChunks tuning can recover.
Making dependency depth >2 a design defect is a useful rule most developers violate without realizing, and it ties directly to RTT accumulation that no CDN can mask.
Chunk splitting must also weigh the scope of cache invalidation.