Granular chunking in Next.js and Gatsby cuts duplicate JavaScript
Next.js and Gatsby have both shipped a newer webpack chunking strategy by default that reduces the amount of duplicate code downloaded across routes. The change replaces the older "single commons bundle" pattern with a more granular approach that emits multiple shared chunks, improving page load performance and cache efficiency.
Why the single commons bundle falls short
Webpack v3 introduced CommonsChunkPlugin, which pooled modules shared between entry points into one (or a few) "commons" chunk. That pattern was widely adopted by single-page application frameworks. The idea was that shared code could be fetched once, cached, and reused across routes. But it had a flaw: modules that weren't shared by every entry point still ended up in the common chunk, meaning a route like page1 could download moduleC even when it never used it.
Webpack v4 removed CommonsChunkPlugin in favor of SplitChunksPlugin, which creates multiple split chunks based on conditions to avoid duplicated code. However, many frameworks continued applying a single-commons heuristic on top of it. Next.js, for instance, generated a commons bundle containing any module used in more than 50% of pages, plus all framework dependencies (react, react-dom, and so on). Adjusting that ratio only traded one problem for another: lowering it downloaded more unnecessary code, while raising it duplicated code across more routes.
Next.js adopts granular splitting
Next.js moved to a different SplitChunksPlugin configuration to reduce unnecessary code for any route:
- Any sufficiently large third-party module (greater than 160 KB) is split into its own individual chunk.
- A separate
frameworkschunk is created for framework dependencies (react,react-dom, etc.). - As many shared chunks as needed are created, up to 25.
- The minimum chunk size is lowered to 20 KB.
This strategy provides two main benefits. First, emitting multiple shared chunks instead of a single one minimizes unneeded or duplicate code for any entry point, which improves page load times. Second, splitting large libraries and framework dependencies into separate chunks reduces the chance of cache invalidation, since those are unlikely to change until an upgrade. The full configuration is available in Next.js's webpack-config.ts.
The request count question
One reason frameworks held onto a single commons bundle was the concern that more chunks mean more HTTP requests, which could slow things down. Browsers typically cap TCP connections per origin (six in Chrome), so keeping the number of chunks below that threshold seemed safer. But that logic only applies to HTTP/1.1. HTTP/2 multiplexing allows multiple requests to stream in parallel over a single connection, and all major browsers support it.
The Chrome and Next.js teams tested the impact by measuring a single page's performance while varying maxInitialRequests. In average runs across multiple trials, load, start-render, and First Contentful Paint times stayed roughly the same when varying the max initial request count from 5 to 15. A slight performance overhead appeared only after splitting aggressively into hundreds of requests.
That suggested a reliable threshold of about 20–25 requests struck the right balance between loading performance and caching efficiency. From baseline testing, 25 was selected as the maxInitialRequest count. Splitting into multiple shared chunks and separating them appropriately per entry point significantly reduced unneeded code for the same page. The total JavaScript needed to hydrate the page remained about the same, which explained why page load performance mostly stayed flat despite the smaller payload.
Webpack's default minimum chunk size is 30 KB, but pairing a maxInitialRequests value of 25 with a 20 KB minimum improved caching results.
Serving granular chunks to the client
Next.js relies on client-side routing that injects new script tags per route transition. To know at build time which chunks each route needs, Next.js determines that from a server-side build manifest file. An abridged client-side version was added to map all dependencies for every entry point so the client can fetch the right dynamic chunks ahead of each navigation.
The granular chunking strategy first shipped behind a flag. Early adopters saw significant reductions in total JavaScript for their full sites, and it became the default in Next.js version 9.2.
Gatsby follows suit
Gatsby used the same usage-based heuristic for defining common modules. After optimizing its webpack configuration to adopt a similar granular chunking strategy, it also saw sizable JavaScript reductions on many large sites. The change shipped by default in version 2.20.7.
Applying the pattern elsewhere
The concept isn't limited to Next.js, Gatsby, or even webpack. Any application following a large "commons" bundle approach should consider improving its chunking strategy, regardless of the framework or bundler used. For a vanilla React reference, a sample app with a simplified version of the granular chunking strategy is available; for Rollup, chunks are created granularly by default, with manualChunks available for manual configuration.



