7 MIN READ · Pedro Thomaz

The JavaScript framework performance cost you never put on the invoice

The real JavaScript framework performance cost isn't the download — it's parse time on cheap phones, hydration, energy, and years of churn. Here's how we measure it.

The JavaScript framework performance cost you never put on the invoice

The JavaScript framework performance cost that sinks a project is almost never the one on the bundle-size badge. The 45 kB gzipped number is the cheapest, most visible, least important part of the bill. The expensive parts are parse and execute time on a four-year-old Android, the hydration tax you pay on every navigation, the energy a million of those page loads burn, and the maintenance churn that eats a day a quarter forever. Those costs don't show up in any benchmark you'll see in a framework's README, so almost nobody prices them in.

We do, because we ship server-rendered PHP with no build step and still have to justify, in writing, every kilobyte of client JavaScript we add. This is the accounting we use.

The short version: what "framework performance cost" actually means

When people say a framework is "fast", they usually mean one of: it renders quickly in a dev-server demo on a MacBook, or its gzipped bundle is small. Neither is the cost that matters to a real visitor on a real phone. The true cost is five line items:

You can have a tiny bundle and still fail all of the others. Transfer weight is the one cost that's trivial to fix with a CDN and the one everyone obsesses over; the other four are where projects quietly rot.

1. Bundle weight is a decoy

Cloudflare sits in front of every site we run, so a 200 kB JavaScript bundle compresses, caches at the edge, and arrives in a few dozen milliseconds for most visitors. If transfer were the whole story, framework weight would be a rounding error.

It isn't, because the byte count and the cost of those bytes are different currencies. A 100 kB image and 100 kB of JavaScript are not equivalent: the browser decodes the image off the main thread and is done. The JavaScript has to be parsed, compiled, and executed on the main thread — the same thread that handles taps, scrolls, and rendering. Bytes of script are the most expensive bytes you can ship, and the bundle badge prices them as if they were the cheapest.

2. Parse and execute time is measured on the wrong device

Here's the line item that gets skipped. Parsing and compiling JavaScript is CPU work, and CPU is the resource that varies most across the devices that actually load your site. A median Portuguese visitor is not on the M-series laptop the framework was demoed on; a meaningful slice is on a sub-€200 Android two or three years old, where the JS engine runs a fraction of the speed.

The same 300 kB of framework + app code that parses in ~50 ms on a developer machine can take several hundred milliseconds to over a second on a low-end phone, and that time is spent on the main thread before the page is interactive. This is the gap between a Lighthouse score run on your laptop and the field data in the Chrome User Experience Report. We've watched a "95 in the lab" turn into a wince on real-device throttling more than once.

Our rule: profile JavaScript-heavy work in Chrome DevTools with CPU throttled to 4x or 6x slowdown, on a network profile of Slow 4G. If it isn't comfortable there, it isn't done. The MacBook number is marketing; the throttled number is the truth.

3. Hydration is paying twice for the same HTML

Hydration is the step where a client framework takes server-rendered HTML and attaches its own event listeners and state so the page becomes interactive. The definition matters because the cost is invisible in a screenshot: the page looks done while the framework walks the entire DOM tree, rebuilds its virtual representation, and re-runs component logic the server already ran.

On a content-heavy page this is pure waste. The server produced correct, complete HTML; hydration's job is essentially to make a static document clickable, and it bills you the full render cost a second time on the client. For a marketing site, a case study, a journal post — most of what a studio like ours builds — there is genuinely nothing to hydrate. The HTML is the product.

This is why our default for content is no client framework at all. Server-rendered HTML, a CDN, and a few kilobytes of hand-written vanilla JavaScript for the two or three things that genuinely need it (a menu toggle, the analytics beacon, lazy-loading a Matterport embed). Zero hydration cost because there is nothing to hydrate. When a project genuinely needs rich client state — a configurator, the recommender UI we built for Jofit — we reach for islands: ship interactivity only to the components that need it, leave the rest as static HTML.

4. The cost nobody invoices: energy and carbon

Every millisecond of parse and execution is a device somewhere burning electricity. One page load is negligible. The traffic across a site's lifetime is not. If a framework adds 400 ms of CPU work per load and a page is served a few million times a year, you are spending real joules on work the server could have done once.

This isn't abstract for us — we run a sustainability discipline and a cookieless analytics setup precisely so we can reason about it. The carbon math is simple and uncomfortable: client JavaScript moves computation from one well-managed server to thousands of uncontrolled devices, and you can't optimise hardware you don't own. The greenest request is the one that arrives as finished HTML and asks the device to do nothing but paint it. Sending less script is the single most effective web-carbon lever we have, and it happens to be the same lever that helps Core Web Vitals.

5. Maintenance churn is a recurring charge

The cost that compounds is the one you sign up for on day one and pay every quarter after. A modern framework drags a dependency tree of hundreds of transitive packages, a build toolchain, and a major-version cadence that does not care about your release schedule. Adopt it and you've committed to migrations, deprecation warnings, breaking changes in tools you didn't choose, and a node_modules you have to keep auditing for vulnerabilities.

Our PHP-and-vanilla stack has a different failure mode and we're honest about it: we write more code by hand, and we don't get a framework's component ecosystem for free. The trade we made is that the platform underneath — the language, the browser APIs — moves on a five-to-ten-year horizon, not a six-month one. The journal post you're reading will render correctly with zero maintenance for years. The cheapest dependency to maintain is the one you never added.

How this ties to Core Web Vitals

The five costs above map almost one-to-one onto Google's Core Web Vitals, the field metrics that gauge real-user experience:

Our target is a Lighthouse score of 95 or above, and — more importantly — passing CrUX field data, not just a green lab run. We hit it by treating client JavaScript as a liability to be justified rather than a default to be assumed. Most pages we ship carry single-digit kilobytes of script. The fastest framework is the code you didn't ship.

So when do we reach for a framework?

This isn't framework-nihilism. A complex, stateful application — a dashboard, an editor, a real-time tool — is exactly what client frameworks were built for, and reimplementing one by hand in vanilla JS is its own expensive mistake. The point is to make the choice on evidence, not habit.

The question we ask on every project is simply: what is the interactivity budget, and does this page spend it? A content page's budget is near zero, so it gets near-zero JavaScript. An application's budget is real, so we spend it deliberately — and even then we lean on islands so the framework only runs where state actually lives.

Price all five line items before you commit, not just the one on the badge. The bundle size is the sticker price. Parse time, hydration, energy, and churn are the total cost of ownership — and on a real phone, in the field, over a project's life, that's the bill that actually comes due.