Dev.to WebDev 🛠 Dev 👁 0 📖 5 min read

Thirty-two JavaScript files serve all 79 pages of our site, and two of them are the same 93,022 bytes of animation library under different names

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

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.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.