---
title: How Turbopack chunks your JavaScript
description: Turbopack’s chunking speeds up page loads and enables sharing code across pages.
We shipped new experimental features to improve chunking in Next.js 16.3.

url: "https://nextjs.org/blog/turbopack-chunking"
docs_index: /docs/llms.txt
publishedAt: September 3rd 2026
authors:
  - Sam Poder
---



Open the network tab of this page and you’ll see a list of JavaScript files with seemingly random names:

<ChunkInspector />

Click on one of them, unminify it, and you’ll find something like this:

{/* prettier-ignore */}
```js filename="36wnellv-yn9q.js"
(globalThis.TURBOPACK || (globalThis.TURBOPACK = [])).push([
  "object" == typeof document ? document.currentScript : void 0,
  7284,
  (e) => {
    "use strict";
    function r() {
      for (var e, r, o = 0, t = "", l = arguments.length; o < l; o++) {
        // ...
      }
      return t;
    }
    e.s(["clsx", 0, r, "default", 0, r]);
  },
  395264,
  (e) => {
    "use strict";
    var r = e.i(7284);
    let o = function () {
      // ...
    };
  },
]);

//# debugId=e60a5ad0-3ac6-bb3a-e12b-6130e99fb7a6
```

That's a chunk. Turbopack generates dozens of them for your Next.js app, holding your own code, the packages you depend on, and the runtime that wires it all together.

“Chunking” is the process of deciding which code goes into which chunk. There are a lot of ways to do chunking, each with their own tradeoffs.

Let's start with the simplest approach: one chunk containing all the JavaScript your app uses.

<ChunkDiagram variant="single" />

Now that chunk is loaded for every page. And because every page shares the same chunk, every load after the first is a cache hit. Neat. Navigation will be fast.

You’ve probably noticed the tradeoff already, however. Loading a page with little to no JavaScript still means loading the JavaScript for every other page. Ugh. As your site grows, every page load gets heavier and heavier. This isn't feasible in the long run.

We could switch to one chunk per page. That’d keep chunks slim and we’d never over-ship code. Perfect!

<ChunkDiagram variant="per-page" />

But then we'd lose caching. If every page uses `<Footer />`, its code ends up inside every page's chunk. A visitor who opens four pages downloads the same footer code four times.

So we need something even more fine-grained. What if every module got its own chunk? Nothing would ever be over-shipped, and a module shared between pages would only download once.

<ChunkDiagram variant="per-module" />

Great, one small issue: that's hundreds of network requests for tiny JavaScript files. Each one carries overhead, and hundreds of them will slow the site down. HTTP/2 has made requests cheaper, but each request still carries overhead. Compression algorithms (e.g. gzip) also perform worse with many small files because they can only find repeated patterns within a single file. Compression dictionaries help, but do not solve this problem.

## Fewer requests or less code

So here’s the challenge we’re left with: we want the lowest possible download size and the fewest requests. Unfortunately, those two goals fight against each other. Fewer, bigger chunks mean fewer requests, but the bigger they get, the less reusable they are across pages.

Turbopack’s solution is to merge smaller chunks into larger ones. The hard part is deciding which ones, and when merging is truly beneficial.

To get there, we’re going to need to introduce a new term, **chunk group**. A chunk group is a collection of chunks that are loaded together. For example, all of the chunks used for `/home` belong to one chunk group and `/blog` another:

<ChunkGroupDiagram />

Chunk groups solve the over-shipping problem. Turbopack only merges chunks from the same group, and chunks in a group always load together anyway, so merging them can't add anything the page wasn't already downloading.

But how do we optimize for cache hits without overloading the browser with requests?

To work that out, picture two kinds of visits: one where you visit a page and leave, and one where you visit a page and then navigate to another page. The same analysis extends naturally to sessions with three or more page visits.

For those cases, let’s walk through the cost or benefit of merging the chunks for `<Footer />` and `<VideoPlayer />`. Call the `<Footer />`'s chunk `A` and the `<VideoPlayer />`'s chunk `B`.

`A` is used on every page, and `B` is only used on the home page. Both are in the home page's chunk group, so they're candidates for merging.

If a visitor loads the home page and leaves, merging always wins. It's one request instead of two, and both chunks were needed anyway.

If they load a second page, it depends on whether that page needs both chunks or just one. Say they go from the home page to the legal page. The legal page needs `A` but not `B`. Because `A` and `B` were merged into one file, the merged file is useless there, so the browser downloads `A` again on its own. The visitor has now downloaded `A` twice.

Merging only pays off across a navigation when both pages need both chunks. Then the merged file is reused, and you've saved a request.

Here are the other possible navigation scenarios. Each row below is a two-page session, written as what the first page needs, then what the second page needs. The numbers show how merging `A` and `B` changes total requests and code downloaded across that session, compared with keeping them separate.

| Navigation        | Change in requests | Change in code downloaded |
| ----------------- | ------------------ | ------------------------- |
| `A` → `A`         | 0                  | 0                         |
| `A` → `B`         | 0                  | 0                         |
| `B` → `B`         | 0                  | 0                         |
| `A` → `A + B`     | 0                  | + `A`                     |
| `B` → `A + B`     | 0                  | + `B`                     |
| `A + B` → `A`     | 0                  | + `A`                     |
| `A + B` → `B`     | 0                  | + `B`                     |
| `A + B` → `A + B` | −1                 | 0                         |

When considering merging chunks `A` and `B`, we calculate the number of chunk groups that only use `A`, only use `B`, and use `A + B`. We weight the cost or benefit of merging by the probability of each scenario.

Lastly, we weight the cases of visiting one page and visiting two pages by their probabilities. We estimate <span className="diagonal-fractions">2/3</span> of sessions are a single page, and <span className="diagonal-fractions">1/3</span> involve two or more.

## Three chunking strategies

Here’s a comparison between the versions of chunking we discussed.

To generate the numbers for each, I visited `nextjs.org` with [three different Turbopack chunking configurations](/docs/app/api-reference/config/next-config-js/turbopackChunking): never merge, the defaults, and merge everything within each chunk group.

On each version I ran the same series of navigations, measuring requests and client-side JavaScript downloaded.

<div className="[&_table]:tabular-nums [&_th:first-child]:min-w-0 [&_td:first-child]:text-gray-900">

|     | Navigation                             | No merging              | Turbopack’s defaults    | One chunk per group     |
| --- | -------------------------------------- | ----------------------- | ----------------------- | ----------------------- |
| 1   | `nextjs.org` (initial page load)       | 363.6 KiB (76 requests) | 344.2 KiB (24 requests) | 315.3 KiB (6 requests)  |
| 2   | `/blog`                                | 35.0 KiB (4)            | 35.0 KiB (3)            | 34.0 KiB (2)            |
| 3   | `/blog/next-16-3-turbopack`            | 6.0 KiB (2)             | 8.7 KiB (1)             | 38.3 KiB (1)            |
| 4   | `/learn`                               | 7.4 KiB (5)             | 6.6 KiB (2)             | 6.6 KiB (2)             |
| 5   | `/learn/dashboard-app`                 | 109.6 KiB (3)           | 115.0 KiB (3)           | 142.0 KiB (1)           |
| 6   | `/learn/dashboard-app/getting-started` | 0 KiB (0)               | 0 KiB (0)               | 0 KiB (0)               |
| 7   | `/showcase`                            | 24.6 KiB (2)            | 25.5 KiB (2)            | 25.0 KiB (1)            |
| 8   | `/docs`                                | 15.4 KiB (4)            | 19.9 KiB (3)            | 48.8 KiB (2)            |
|     | **Overall**                            | 561.6 KiB (96 requests) | 554.8 KiB (38 requests) | 610.0 KiB (15 requests) |

</div>

As you can see, chunking is a balancing act. On [nextjs.org](https://nextjs.org/), defaults cut requests by more than half versus no merging while shipping slightly less code; maximum merging cut requests further but shipped 10% more code overall. However, if we'd navigated less, maximum merging would have been more optimal. Merging every chunk in a group into one large chunk delivered benefits on the initial page load but was costly in the long term.

## New chunking features in Next.js 16.3

Two things limit how well chunking works. Merging is decided at build time, before anyone visits, so it can't react to what a browser already has cached. And the algorithm has to guess at how people move through your site, which is why single-page visits get weighted at <span className="diagonal-fractions">2/3</span>. This summer, as part of my internship on the Turbopack team, I worked on both, along with trimming what ends up in the chunks in the first place.

### Smarter chunk fetching

The table above showed the cost of merging. When a visitor loads a page with `A` and then one with `A + B`, merging makes them download `A` twice. The bundler can't avoid that, because at build time it doesn't know what the browser already has. The runtime does.

In Next.js 16.3 or later, enabling `experimental.turbopackChunking.generateComponentChunks` in your `next.config.js` makes Turbopack emit un-merged versions of chunks alongside the merged ones. We track which items make up each merged chunk and which of those have already been loaded, so at request time we can pick whichever is cheaper: the merged chunk, or just the pieces that are missing.

It works in reverse too. If a visitor has already loaded the merged `A + B`, we can skip re-loading `A` on its own.

Soft navigations then load less unnecessary code, and we get the benefits of merging without the navigation cost.﻿

Take a look at this demo app to see the difference:

<div className="not-prose my-6 grid gap-6 md:grid-cols-2 [&>div>div>div]:my-0">
  <div>
    <span className="mb-2 block font-mono text-[11px] text-gray-900">
      generateComponentChunks: false
    </span>
    <div className="dark-theme:hidden">
      <Demo
        height={360}
        src="https://component-chunks-demo-off.labs.vercel.dev/"
        title="Soft navigation with merged chunks only"
      />
    </div>
    <div className="hidden dark-theme:block">
      <Demo
        height={360}
        src="https://component-chunks-demo-off.labs.vercel.dev/?dark"
        title="Soft navigation with merged chunks only"
      />
    </div>
  </div>
  <div>
    <span className="mb-2 block font-mono text-[11px] text-gray-900">
      generateComponentChunks: true
    </span>
    <div className="dark-theme:hidden">
      <Demo
        height={360}
        src="https://component-chunks-demo-on.labs.vercel.dev/"
        title="Soft navigation with component chunks emitted alongside merged chunks"
      />
    </div>
    <div className="hidden dark-theme:block">
      <Demo
        height={360}
        src="https://component-chunks-demo-on.labs.vercel.dev/?dark"
        title="Soft navigation with component chunks emitted alongside merged chunks"
      />
    </div>
  </div>
</div>

We're also experimenting with the [`only-if-cached`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control) directive, which lets us check what a visitor has cached when they arrive. That would extend the same improvements to people who leave a site and come back later.

### Analytics-based chunking

You may have noticed that we make a lot of assumptions about websites in our chunking algorithm. For example, the <span className="diagonal-fractions">2/3</span> weighting is a guess. It’s a reasonable default across many sites, but not necessarily the right one for any given site. If you know how people actually move through yours, you can tell us.

In your `next.config.js`, you can now configure these under `experimental.turbopackChunking`:

- `firstPageLoadPriority`: shifts the weighting between the one-page and two-page cases. A higher value (between 0 and 1) prioritizes a fast initial load, potentially at the cost of navigation. Bounce rate is a reasonable starting value. We default to 0.67.
- `priorityRoutes`: a list of pages whose load speed matters most. We'll merge chunks on these routes more opportunistically.
- `clusters`: groups of routes commonly visited together, each defined by an array of regular expressions. We'll merge overlapping chunks more readily inside a cluster. But if a cluster mixes pages that use just `A` or just `B` with pages that use both, we'll merge less.

### Smaller and fewer chunks

Everything above is about how to group code. This last set of features is about shipping less of it in the first place. I worked on the following features to support this:

- **Tree-shaking CJS modules**: We only supported this for ESM before, so unused imports and exports in CJS modules were shipping to the client. Enable it with `experimental.turbopackCjsTreeShaking`. It'll be on by default in a future version of Next.js.
  - I also expanded the set of ESM modules we can analyze and tree-shake. This included improving [support for barrel files in dynamic imports](https://github.com/vercel/next.js/pull/95989).
- **A shared Turbopack runtime**: One runtime chunk now replaces the per-page ones. Enable it with `experimental.turbopackSharedRuntime`. It saves a blocking request and about 10 KB of client-side JavaScript on every navigation after the first. It'll also be on by default later.
- **A lighter default runtime**: The runtime no longer ships WebAssembly and Web Worker code by default. We detect when you're using those modules and insert the loading code then.

### Try it out

Try out these new features in Next.js version 16.3 or later. And if you're interested in learning more about chunking (including CSS chunking), check out [Tobias](https://twitter.com/wSokra)' [recent talk on the tradeoffs and constraints of chunking](https://gitnation.com/contents/turbopack-updates).
