Performance-Fresser ■ Episode 04
React 18 ships
136 KB minified
before you have written a single line of application code. That is
react plus react-dom, the absolute minimum
to render a component. One hundred and thirty-six kilobytes of framework,
delivered to a browser that must parse every byte, just to say hello.
The industry quotes 42 KB. That is the gzipped transfer size, the
number that travels over the wire. It is also, with respect, the wrong number.
Gzip is a transport optimisation. The browser does not execute gzipped code.
It decompresses the bundle and then parses 136 KB of JavaScript, character by
character, before your application can so much as mount a
<div>.
One does not measure the weight of a parcel by the size of the envelope.
The Virtual DOM: Pure Overhead
React’s central architectural conceit is the Virtual DOM, a JavaScript copy of the actual DOM, maintained in memory, against which every state change is diffed before the real DOM is updated. The pitch is compelling: surgical updates, minimal reflows, maximum efficiency. The reality is rather less flattering.
Rich Harris, creator of Svelte, put it with characteristic precision:
“Virtual DOM is pure overhead.”
The argument is structural, not polemical. Every state change triggers three operations: build a new Virtual DOM tree, diff it against the previous tree, and patch the real DOM with the delta. Two of those three steps exist solely because the framework chose an indirection layer. A compiled framework, or vanilla JavaScript, updates the DOM directly. One step. No diff. No reconciliation. No overhead.
Benchmarks bear this out with tedious consistency. The Virtual DOM approach carries roughly a 30% performance penalty compared to equivalent vanilla JavaScript. Not on contrived micro-benchmarks, but on the bread-and-butter operations that constitute a user interface: rendering lists, toggling visibility, updating text content. Thirty per cent, on every interaction, forever.
The standard rebuttal is that React is “fast enough.” This is true in the same sense that a taxi is “fast enough” for a journey you could walk. The question is not whether it arrives. The question is what you are paying for the ride.
The Cascade of Re-renders
React’s rendering model contains a default that is, to put it diplomatically, surprising. When a parent component re-renders, every child component re-renders as well. Not because the children’s data changed. Not because the children requested an update. Simply because the parent did, and React’s reconciler does not, by default, ask whether the children care.
The framework provides escape hatches: React.memo,
useMemo, useCallback. These are performance
optimisations that exist to mitigate a performance problem the framework
created. The developer must identify which components re-render unnecessarily,
wrap them in memoisation, manage dependency arrays, and hope the closure
semantics do not introduce bugs more expensive than the re-renders they
prevented.
In a well-behaved rendering model, a component updates when its inputs change. In React’s rendering model, a component updates when its parent feels like it. The distinction is the difference between a thermostat and a central heating system that runs whenever someone opens the front door.
Hydration: The Redundancy Engine
Server-side rendering was supposed to solve React’s initial load problem. The server renders HTML, the browser displays it immediately, and the user sees content before the JavaScript arrives. An elegant theory. Then hydration happens.
Hydration is the process by which React downloads its 136 KB, parses it, reconstructs the entire component tree in memory, walks every DOM node the server already rendered, and attaches event handlers. The server sent perfectly functional HTML. React then spends up to 30% of total page load time rebuilding it from scratch, not to change what the user sees, but to make the framework aware of what is already there.
The engineering team at Wix measured this directly and achieved a 40% improvement in interaction time by optimising hydration alone. Forty per cent faster, not by adding functionality, but by reducing the cost of functionality they already had.
The absurdity is architectural. The server does the work. The browser displays the result. Then React does the work again, on the client, with JavaScript, so that it can take credit for what the server already produced. One might call it due diligence. One might also call it a protection racket.
The Pipeline
Consider the journey of a single piece of markup in a React application. It begins as HTML in a designer’s mind, is expressed as JSX, transpiled by Babel, bundled by Webpack, minified, shipped over the network, decompressed, parsed by the JavaScript engine, executed to produce a Virtual DOM, diffed against the previous Virtual DOM, and finally, at last, rendered as HTML in the browser.
The output is what the input was.
Eight steps to arrive where you started. The pipeline exists not because HTML requires it, but because the framework requires it. Remove the framework and the pipeline collapses to a single step: write HTML, serve HTML. The browser has been rather good at that since 1993.
The Market Delusion
The Stack Overflow 2024 Developer Survey reports that 39.5% of developers use React. An impressive figure, until one consults the other survey. W3Techs, which measures actual website usage rather than developer self-reporting, places React at 6% of all websites. Not 39.5%. Six.
The gap between these two numbers is the gap between what developers are trained to use and what the web actually needs. And the web, for the overwhelming majority of its surface area, needs documents. Not applications. Documents.
An estimated 95% of websites do not need the capabilities of a Single Page Application. They are marketing sites, blogs, documentation portals, e-commerce catalogues, documents with the occasional interactive element. The browser handles documents natively. It has done so for three decades. It is, one might venture, rather good at it.
Yet 39.5% of developers reach for React. The framework is not chosen because the problem demands it. It is chosen because the developer knows it. The recruitment pipeline selects for React experience. Bootcamps teach React because companies require React because bootcamps teach React. The loop is self-sustaining, economically rational for every participant, and structurally detached from the question of whether the user needed 136 KB of JavaScript to read a product description.
The Netflix Evidence
When the arguments are theoretical, they are easy to dismiss. When Netflix runs the experiment, they are rather less so.
Netflix removed React from their logged-out landing page and replaced it with vanilla JavaScript. The results were not ambiguous.
Time to Interactive improved by 50%. The bundle dropped by 200 KB. The page did the same thing it had always done (display a hero image, a few headings, a sign-up form) except now it did so without waiting for a framework to reconstruct what the server had already delivered.
Netflix did not remove React from their application. The logged-in experience, the streaming interface where users browse, search, and watch, still uses it. The distinction matters. The logged-in experience is an application. Users stay for hours, interact hundreds of times, and the framework cost amortises across a long session. The landing page is a document. Visitors glance, decide, and either sign up or leave. The framework cost does not amortise. It simply arrives, uninvited, and leaves with 200 KB of the user’s bandwidth.
Where React Earns Its Keep
This is not, to be clear, a call to abolish React. The framework genuinely solves problems. Specific problems, for specific applications, under specific conditions.
Gmail. Google Docs. Figma. Spotify’s web player. Applications where users remain for hours, where the interface is a dense, stateful, interactive surface that changes constantly. Applications where the cost of 136 KB is amortised across thousands of interactions. Applications where client-side state management is not a luxury but a structural necessity.
These exist. They are real. And they represent, at a generous estimate, perhaps 5% of what gets built with React.
The remaining 95% are documents dressed in application clothing. Marketing sites. Blogs. Product catalogues. Landing pages. Content that the server could have rendered as HTML, that the browser could have displayed natively, that the user could have seen immediately, had the team not decided that a document needs a Virtual DOM, a reconciliation engine, and a hydration ceremony before it is permitted to appear on screen.
The Invoice
Let us itemise.
136 KB of framework, parsed on every visit. 30% performance overhead from Virtual DOM diffing. 30% of page load time consumed by hydration. A rendering model that re-renders every child when a parent changes. A pipeline of eight transformations to produce what the server could have shipped directly. A market adoption rate six times higher among developers than among actual websites.
Every one of these costs is documented. Every one is measurable. And every one is paid, silently, by the user: in loading time, in battery drain, in data consumption, in the fraction of a second between tapping a link and seeing content that determines whether they stay or leave.
The fastest framework is the one you do not ship.
React is a solution. An excellent one, for applications that justify its weight. The question is not whether React works. The question is whether your website is an application. If the answer requires more than a moment’s thought, it probably is not. And the 136 KB you just shipped to a user on a 3G connection in Lagos, or a budget Android in Jakarta, or a perfectly adequate broadband line in Ludwigshafen, that is not a framework choice. That is a tax. And unlike most taxes, this one does not fund anything the user asked for.