Nothing fails when a page gets heavier

Generated markup is verbose, imports whole libraries for one function, and inlines assets because that works. Every test still passes. The cost lands on a phone on a slow connection, months later, as a number with no cause attached.

article By 6 min read

There is no red test for a page that got 40KB heavier. There is no red test for the twelfth nested div, for import _ from "lodash" where Array.prototype.at would do, or for a hero image pasted into the HTML as a base64 data URI because that made it work without touching the asset pipeline.

All of those are the output of a competent, fast writer optimising for the thing it was asked to optimise for: producing something that works. And each one is invisible individually. That is the whole problem. Page weight has no failure event. It has a slope.

What generated markup actually does

Three habits, all of them locally reasonable.

Verbosity. Generated markup tends towards wrapper elements and long utility class strings, because that is what most published markup looks like and because nesting is the safe way to get a layout right without understanding the surrounding CSS. Compression handles the transfer side of this well — repeated class strings are exactly what gzip is good at — which is why it is easy to dismiss. The part compression does not touch is the DOM: more nodes to build, more to style, more to lay out, and every subsequent recalculation is over a larger tree. A page with six thousand nodes and a page with fifteen hundred behave differently on a mid-range Android phone in a way they do not on a developer's laptop.

Whole-library imports. An agent asked to format a date reaches for the library it has seen used to format dates. Modern bundlers tree-shake, so sometimes this is free — and sometimes it is not, because the library has side effects, or a CommonJS build, or is pulled in by a path the bundler cannot statically analyse. The failure is not that it always costs; it is that it sometimes costs and nothing distinguishes the two cases at review time. A diff that adds one import line looks the same either way.

Inlining. Inlining an asset removes a request, which is a real optimisation in some situations, and there is a lot of published advice saying so. But base64 is about a third larger than the bytes it encodes, an inlined asset cannot be cached separately from the document, and it is re-downloaded on every page view, and it sits in the critical path of the HTML rather than being fetched in parallel. A 200KB inlined hero image is a document that cannot start rendering until 270KB has arrived.

None of this is incompetence. It is what the instruction "make this work" produces, at a rate no review process was sized for.

The mechanism, stated carefully

The path from bytes to money is real but it is longer than most posts about it admit, so here it is with the joints exposed.

Bytes and main-thread work become Core Web Vitals. Largest Contentful Paint is when the main content element renders, and it moves directly with how long the critical resources take to arrive and parse. Interaction to Next Paint measures how long the page takes to respond to a tap, and it degrades when the main thread is busy — parsing and executing script is the usual culprit, and a large DOM makes every style and layout pass afterwards more expensive. Cumulative Layout Shift is only weakly about weight, but late-arriving fonts and un-sized images are a common way generated markup earns it.

Core Web Vitals are a ranking signal, and a small one. This is the part to be precise about, because it is routinely oversold. Google has said page experience is used in ranking, and has also said it does not override having the most relevant answer. A slow page about exactly what someone searched for will outrank a fast page about something else. If your page is already the best answer, shaving 300ms will not move you from position eight to position one, and anyone who tells you otherwise is selling something. Where it plausibly matters is between pages that are otherwise comparable, which is a real situation and a narrower one than the usual pitch implies.

Weight costs you visitors directly, and that link does not route through a search engine at all. Someone on a phone, on a bad connection, looking at a blank screen, leaves. You do not have to take that on faith or on a vendor's statistic: it is measurable in your own analytics, by load time bucket, against your own funnel. That is the number worth pulling before you decide how much this is worth to you.

So: the ranking effect is real and modest, and the abandonment effect is the one you can measure yourself.

Two settings that mitigate a surprising fraction of it

Before rewriting any markup, check the two settings that cost nothing.

Compression. Brotli or gzip on text responses — HTML, CSS, JavaScript, SVG, JSON — is a large multiple on the transfer size of exactly the verbose output described above, and it is a header and a config directive. It is also routinely missing on one path and present on another: enabled at the CDN, absent on the origin that serves the API; on for .js but not for the .mjs a newer build emits; on for the HTML but not for the JSON payload the page fetches immediately afterwards. The gap is rarely where you looked.

Cache policy. The second visit should not pay for the assets again. A fingerprinted asset — one with a content hash in the filename — can be served Cache-Control: public, max-age=31536000, immutable safely, and the HTML document should be revalidated rather than cached hard. Getting this backwards is the classic error, and it produces the two worst outcomes available: users holding a stale page for a year, or re-downloading everything on every navigation. An inlined asset, incidentally, cannot participate in any of this.

compression and cache-policy are both HarpyWatch checks — ours, so discount accordingly — for the ordinary reason that both are properties of the response a client receives rather than of the config you wrote. page-weight records what actually arrived, so the slope is a series rather than a memory, and core-web-vitals measures the rendering outcome those bytes produce.

The limit worth stating plainly: a scheduled check from a fixed location is a lab measurement. It is excellent at telling you that something changed and roughly when — which is the question you cannot otherwise answer — and it is not a substitute for field data from your actual users on their actual devices. Two different instruments. Use both if you have both.

The point is the derivative

You do not need an alert on page weight. You need to know the number is going up, and roughly when it started, because the answer to "why is the site slow now" is almost always a commit, and finding it is trivial if you know the week and impossible if you do not.

Nothing fails when a page gets heavier, so unless something is watching the number, the first signal is a quarterly conversion report with no explanation attached. Bisecting four hundred commits to find the cause is not impossible. It is just expensive work to recover a fact that was free to record at the time.

Cite this

The HarpyWatch team. “Nothing fails when a page gets heavier”. The Nest, HarpyWatch, 13 October 2026 UTC. https://harpywatch.com/blog/nothing-fails-when-a-page-gets-heavier

More from The Nest

The security header an agent widened to make the feature work

A strict Content-Security-Policy is usually the thing standing between an agent and the feature it was asked to add. Widening it is the path of least resistance, it is never mentioned in the summary, and nothing about the site looks different afterwards.

article 6 min read

Set it up in an afternoon and then stop

Adopting monitoring for a small site is five decisions, not a project. Here is what each one actually involves, and why finishing a rough version beats designing a perfect one.

article 6 min read

Staging configuration travels, and agents help it

A canonical tag pointing at staging, a noindex that shipped, a test API key in production. Each is invisible on a developer's machine and expensive live, and copying config between environments is the fastest way to make something work.

article 6 min read