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

Our HTML names the same 32-character deployment id 6,052 times, and its job is to discard 456 KB of correctly cached JavaScript

Open any page on pub-trivia.app, view source, and every reference to a static file looks like this: <link rel="stylesheet" href="/_next/static/chunks/3lcp5uer5uji0.css?dpl=dpl_EKzaTAmQUBUdseoodG43waJqVSuS"/> <script s

Open any page on pub-trivia.app, view source, and every reference to a static file looks like this:

<link rel="stylesheet" href="/_next/static/chunks/3lcp5uer5uji0.css?dpl=dpl_EKzaTAmQUBUdseoodG43waJqVSuS"/>
<script src="/_next/static/chunks/2npd4_bol1mt_.js?dpl=dpl_EKzaTAmQUBUdseoodG43waJqVSuS" async=""></script>

dpl_EKzaTAmQUBUdseoodG43waJqVSuS is the id of the deployment currently serving the site. It is 32 characters, it is the same on every URL, and I counted it across all 72 public pages:

Occurrences of ?dpl= 6,052
Per page, lowest 44
Per page, median 84
Per page, highest 146
Raw bytes 218.6 KB
Share of all HTML we serve 4.41 per cent

There is a 6,053rd copy that my pattern missed, on the root element of every page:

<html data-dpl-id="dpl_EKzaTAmQUBUdseoodG43waJqVSuS" lang="en">

And two more per response in the Link header of the two per-request pages, preloading the fonts.

Why one page names the same file ten times

The FAQ references 22 distinct static files and mentions /_next/static/ 83 times. The homepage mentions it 145 times for the same 22 files.

The multiplier is the inline React flight payload. A chunk is named once in its <script src> and then again, with the query, inside every serialised client-component reference that needs it:

2:I[880492,["/_next/static/chunks/2npd4_bol1mt_.js?dpl=dpl_EKza...","/_next/static/chunks/0efhw0ogo_md6.js?dpl=dpl_EKza..."],"ThemeProvider"]

2npd4_bol1mt_.js is named ten times in one document. None of those ten repetitions is wrong, they are just the same fact written down by two different serialisers that do not know about each other.

The query is decoration

Here is the part that surprised me. I asked for the same stylesheet four ways:

$ for q in "?dpl=dpl_EKzaTAmQUBUdseoodG43waJqVSuS" "" "?dpl=dpl_AAAAAAAAAAAAAAAAAAAAAAAAAAAA" "?dpl=nonsense"; do
    curl -s -o /dev/null -w "%{http_code} %{size_download}  " "https://pub-trivia.app/_next/static/chunks/3lcp5uer5uji0.css$q"
    curl -sI "https://pub-trivia.app/_next/static/chunks/3lcp5uer5uji0.css$q" | grep -i '^age'
  done
200 86718  age: 264629
200 86718  age: 264629
200 86718  age: 264630
200 86718  age: 264630

Four different URLs, the same 86,718 bytes, and the same cache object behind all of them: the age is continuous across the four requests, so the CDN is not keying on the query at all. Nothing validates the id. A request with somebody else's deployment id, or with the word nonsense, gets the current file.

That is not a hole, because there is nothing to protect: the filename is already a content hash, so the bytes you get are decided entirely by the path. It does mean the query is doing exactly one job, and that job is on the client.

What it is for, and what it costs

Static assets are served like this:

cache-control: public, max-age=31536000, immutable

A year, immutable, which is the correct policy for a content-addressed filename. A browser that has 3lcp5uer5uji0.css will never ask about it again.

Until we deploy. Then every URL in every document changes, because the query changes, and 3lcp5uer5uji0.css?dpl=A and 3lcp5uer5uji0.css?dpl=B are two different cache entries holding identical bytes. Measured on the live site:

Files Raw Over the wire with brotli
What one page loads 22 1,344 KB 456 KB
The whole static set 31 1,562 KB 536 KB

So the cost of a deploy, for every returning visitor, is 456 KB of JavaScript, CSS and fonts they already had, byte for byte, under a slightly different name. Our next.config.mjs does not mention deploymentId anywhere. This is the platform adding it, and it is the mechanism behind deployment skew protection: a tab that was loaded ten minutes ago asks for chunks from the build it was rendered by, not from whatever is live now, so a mid-session deploy cannot hand a running page a module that no longer matches its route tree.

That is a real problem and this is a real fix. It is also a trade that depends entirely on how often you ship. Our last deploy was 19 October and the asset in the test above has been in the edge cache for 264,629 seconds, which is three days on that node and twelve days of continuous age on the HTML. At that cadence a cold asset cache per deploy is free. A team shipping twenty times a day is asking every returning visitor to redownload half a megabyte twenty times a day, for safety against a skew window measured in minutes.

The bytes in the HTML are not the problem

218.6 KB of repeated id sounds like something to fix. It is not, and the reason is the same one that made 581 inline icons a rounding error:

Raw brotli -q 11
All 72 pages as served 5,081,288 638,202
With every ?dpl= removed 4,857,438 634,793
Cost of 6,052 copies 218.6 KB 3.3 KB

A 66 to 1 compression ratio, because the 6,052nd copy of a 37-character string is a backreference. Three kilobytes across the entire site.

The expensive thing here was never the text. It is that a correct, content-addressed, immutable caching strategy is invalidated on a schedule that has nothing to do with whether the content changed.

What I am changing

Nothing, and I want to be specific about why, because "nothing" is the honest answer more often than blog posts admit.

  1. The query stays. Skew protection is worth more than one cold cache every ten days, and turning it off to save bytes a compressor already removed would be a bad trade in both directions.
  2. The ten repetitions per chunk stay too. They come from Next's own serialisation of client component references, they compress to nothing, and the alternative is a patch against the framework's flight encoding.
  3. The one thing I will do is watch it if the cadence changes. The moment this project ships daily, 456 KB per returning visitor per deploy becomes a number worth arguing about, and the argument will be about the skew window, not about the HTML.

Check it yourself

Every measurement above is available from outside, with no account:

# how many times one page names the deployment
curl -s https://pub-trivia.app/faq | grep -o '?dpl=' | wc -l

# the id on the root element
curl -s https://pub-trivia.app/faq | grep -oE '<html[^>]*data-dpl-id="[^"]*"'

# the same file, with the query, without it, and with a wrong one
for q in "?dpl=dpl_EKzaTAmQUBUdseoodG43waJqVSuS" "" "?dpl=nonsense"; do
  curl -s -o /dev/null -w "%{http_code} %{size_download}\n" \
    "https://pub-trivia.app/_next/static/chunks/3lcp5uer5uji0.css$q"
done

# a year, immutable, on a filename that is already a hash
curl -sI https://pub-trivia.app/_next/static/chunks/3lcp5uer5uji0.css | grep -i cache-control

# and the font preload, carrying the id in a response header
curl -sI https://pub-trivia.app/pricing | grep -i '^link'

If you would rather look at the product than its query strings, the free tools need no account and no card, and the pricing page is one of the two pages whose preload hints arrive as the header above rather than as HTML.

📰 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.