I pulled all 79 URLs from our sitemap, extracted every script URL from every response, and de-duplicated.
pages 79 distinct /_next/static/chunks/*.js 32 Thirty-two files for the whole site. That is the good news in this post, and it is what you would hope for from a site where 77 of the 79 pages are prerendered prose with the same shell around them. The rest of the post is the two findings I did not expect.
Per page: 14 files to 23
Fetching each page's script list and summing the actual file sizes:
| Page | chunks | identity bytes | brotli |
|---|---|---|---|
| /legal/privacy | 14 | 963,689 | 304,465 |
| /faq | 19 | 1,244,171 | 400,913 |
| /pricing | 20 | 1,248,082 | |
| /tools/round-planner | 20 | 1,253,957 | |
| / | 23 | 1,396,479 | 457,984 |
Two caveats before anybody quotes those numbers at me. First, brotli is what actually crosses the wire, and it is about 32 per cent of identity. Second, one of those files on every page is a 112,594-byte core-js bundle served with noModule="", which no browser that supports modules will ever fetch:
<script src="/_next/static/chunks/0cz1d0mv5g_q7.js" noModule=""> So the honest figure for a modern browser is 41,343 brotli bytes lower than the table, and the spread between the lightest page and the heaviest is 153 KB brotli.
The lightest page in the table is interesting on its own. The three legal pages ship 14 chunks where every other page ships 19 or more. They are missing six that the FAQ has and they carry one of their own that nothing else does, and of those six, three are plainly animation: a 93,022-byte library file, a 48,649-byte layout projection file, and a 41,303-byte one whose only interesting string is MotionConfig. The legal pages get the compact footer, which has no animated wordmark in it, so nothing on those three pages animates and the animation library never enters their graph. Three pages out of 79 escape, and they escape by accident of their footer.
The thing I actually came here to write about
Two pairs among the 32 chunks have exactly matching byte counts. Here is the larger pair:
/_next/static/chunks/02h14kn2-134j.js 93,022 bytes on 75 pages /_next/static/chunks/3kxicag_zvv3l.js 93,022 bytes on 1 page (the homepage) Both contain the animation library. Both reference framer-portal-id, both contain eight references to MotionConfig, and neither is a subset of the other. Their hashes differ, and 89,300 of the 93,022 bytes differ byte for byte.
They are the same code. Here is the first byte where they diverge:
A: ...(e._debugIds={}))[n]="fcf4b9a7-cf55-377b-1bbe-fb0c11772165")}catch(e){}}(); B: ...(e._debugIds={}))[n]="1a6fe0f7-36af-9eb2-9281-23424f1a5951")}catch(e){}}(); A build debug id, at byte 231. Everything after it differs because the module ids differ, which is also where the string literals differ:
distinct string literals in A 521 distinct string literals in B 517 only in A: ',0,h],34771)},813968,e=>{' 'addDomEvent' ... only in B: ')[0].trim())])},402722,526371,e=>{' ';e.s([' ... Numeric module ids, and nothing else of substance. Compressed, the two come out 203 bytes apart: 33,874 and 33,671.
The second pair is even plainer. Two files of 41,303 bytes, one on the homepage and one on everything else, differing in 8,883 bytes, with string literal counts of 252 and 252 and exactly one string present in the first and absent from the second, which is its debug id. They compress to 15,495 and 15,493.
So a visitor crossing from any page on the site to the homepage re-downloads 134,325 identity bytes, 49,369 of them over the wire, of code their browser already has under a different URL.
So a visitor who reads the FAQ and then clicks through to the homepage downloads the animation library twice over. Not two versions of it, not two slices of it. The same library, emitted into two chunk identities by one build, with no shared parent for the bundler to hoist it into.
There is a third motion file, 48,649 bytes of layout projection code, and that one is shared: 76 pages reference the same URL for it. So the splitter hoisted part of the library and duplicated the rest, which is the kind of outcome you only see if you go and count.
I am not going to pretend I know the exact cause from the outside, and the honest state of this is "measured, not yet diagnosed". The homepage is one of only two per-request pages on the site, and it is the one page whose chunk is distinct, which makes a route-group boundary the obvious suspect. The plausible fix is on the import side rather than the config side: everything that animates goes through one local module, and the pages that need motion import that, so there is exactly one place for the bundler to attribute it to.
What the homepage adds
The homepage's six extra chunks, with the markers that identify them:
3kxicag_zvv3l.js 93,022 duplicate of the animation library 2t_9jwasvtpg3.js 63,919 AnimatePresence, LazyMotion, domAnimation, ParticleCanvas 0rjxoie5cw6uq.js 41,303 duplicate of the other same-size pair 1k4uthw3jscw8.js 30,348 ParticleCanvas, cqmin 3zogd6xn3r8fi.js 29,219 ParticleCanvas 1zkdkb6mw9dlt.js 28,822 That is 286,633 identity bytes, 106,235 brotli, and almost all of it is the two demos on the page: a phone running the real player screens and a big screen running the real host view, both fed by a fixture rather than a server. Half of that cost is the duplicated library. The other half is the actual feature.
For a marketing page that is a real number, and I think it is the right trade, because the alternative to shipping the product's own components is shipping a video of them, and a video of a 1,100-pixel-wide dashboard is both heavier and a lie that goes stale. But it is worth knowing that an interactive demo on a landing page is approximately the weight of the whole rest of the page's JavaScript.
Reproduce it
Every number above comes from public responses. The script-list extraction is one grep:
# every chunk the homepage references curl -s https://pub-trivia.app/ \ | grep -o '/_next/static/chunks/[a-zA-Z0-9_.-]*\.js' | sort -u | wc -l # 23 # and the FAQ curl -s https://pub-trivia.app/faq \ | grep -o '/_next/static/chunks/[a-zA-Z0-9_.-]*\.js' | sort -u | wc -l # 19 # the two files that are the same size curl -sI https://pub-trivia.app/_next/static/chunks/02h14kn2-134j.js | grep -i content-length curl -sI https://pub-trivia.app/_next/static/chunks/3kxicag_zvv3l.js | grep -i content-length Chunk names change with every deployment, so if those last two 404 by the time you read this, run the greps above and compare whatever the current pair is. The test that generalises: take the script list for your heaviest page and your lightest page, fetch both lists, and look for files with matching byte counts and different names. On this site there were two such pairs among 32 files. It took me four commands to find a 93 KB duplicate and then a 41 KB one, which is four more than I had spent on it in two months of shipping.
The pages themselves, if you want to watch what the extra 286 KB buys: the homepage demos, and for contrast a guide page that is pure prose and shares every byte of its JavaScript with 75 other pages.