Lean Web ■ Episode 6
“We need React for this.”
One hears the sentence in stand-ups, architecture reviews, sprint plannings, and that particularly charged moment when a project lead opens a whiteboard and draws boxes. It arrives with the quiet confidence of a fact. It is, in most cases, nothing of the sort.
DOM manipulation. Event handling. State updates. HTTP requests. JavaScript solved every one of these in 2015. Not with a framework. Not with a library. With the language itself and the browser APIs that ship in every browser on every device, at zero cost, with no build step, no transpilation, and no dependency tree.
The question is not whether React works. The question is what precisely it does that the browser does not already do for free.
Five Methods
The browser’s native API surface is vast. But for the overwhelming majority of interactive websites (not applications, websites) the daily work reduces to five methods:
document.querySelector(): find an element.
element.addEventListener(): respond to an event.
element.classList.toggle(): change appearance.
fetch(): make an HTTP request.
new WebSocket(): maintain a persistent connection.
Five methods. No imports. No configuration. No package.json. No
node_modules. No build pipeline. The browser understands them
natively, executes them immediately, and has done so, reliably, for over a
decade.
React, by contrast, requires JSX transpilation, a bundler (Webpack, Vite, or
similar), Node.js as a build dependency, a package.json with
a median of 47 direct dependencies, a node_modules directory
containing roughly 1,200 folders, a build step that produces a bundle, and
a hydration phase that reconstructs on the client what the server already
rendered.
The output of this ceremony is a DOM update. The same DOM update that five native methods perform without ceremony at all.
“But Components!”
The first objection arrives like clockwork. “React gives us components. Reusable, encapsulated, composable.” This is true. It is also true that the browser has offered precisely the same capability since 2020, without React, without a build step, and without a single line of JSX.
Web
Components, specifically Custom Elements, are a browser-native
standard. A class extending HTMLElement, a
connectedCallback, and customElements.define(). Three
constructs. Native encapsulation via Shadow DOM. Native lifecycle hooks. Native
attribute observation. No transpiler required. No virtual DOM. No reconciliation
engine.
Browser support stands at 98% globally, which is higher than the support for several CSS features that nobody hesitates to ship.
The distinction is important. React components are a React abstraction. They exist inside React, render through React, and cannot escape React without a wrapper. Web Components are a platform primitive. They work in React. They work in Vue. They work in a static HTML file served from a cupboard. They work because the browser understands them, not because a framework permits them.
“But Reactivity!”
The second objection follows promptly. “React gives us reactivity. Change the state, the view updates automatically.” This is, once again, correct. It is also achievable in eight lines of vanilla JavaScript.
The
Proxy
object, standardised in ES2015 and
supported in
every modern browser, intercepts property access and mutation on any
JavaScript object. Wrap your state in a Proxy, define a set trap
that calls a render function, and you have reactivity. Real reactivity. Not
virtual-DOM-diffing reactivity. Direct, surgical DOM updates triggered by the
actual mutation, with no intermediate tree comparison.
React’s reactivity model builds a new Virtual DOM tree on every state change, diffs it against the previous tree, calculates the delta, and patches the real DOM. Four operations to achieve what a Proxy achieves in one. The overhead is not theoretical. It is measured in milliseconds, in CPU cycles, and in the battery life of every mobile device that must perform the calculation.
The Numbers
Let us be specific about what ships to the user.
React
plus ReactDOM: 142 KB, minified and gzipped. That is the transfer size;
the browser decompresses and parses considerably more. Before your application
renders a single <div>, 142 kilobytes of framework must
arrive, decompress, parse, and execute.
Vanilla JavaScript: 0 KB. It is already in the browser. It shipped with the browser. It is the browser.
Web Components: 0 KB. Same reason. Custom Elements are a browser specification, not a library.
Proxy: 0 KB. It has been part of the JavaScript engine since 2015.
Zero is a compelling bundle size. It is difficult to optimise further. Tree-shaking zero kilobytes yields, one imagines, zero kilobytes. Code-splitting zero kilobytes produces, remarkably, zero kilobytes. The performance budget for the browser’s native APIs is settled before the conversation begins.
The Ecosystem Paradox
React’s ecosystem is enormous. Redux, React Router, React Hook Form, Formik, React Query, Zustand, Jotai, Recoil. The constellation of libraries is vast, well-maintained, and deeply impressive. It is also, structurally, a consequence of what React does not do.
React does not manage global state. So the ecosystem produced Redux, Zustand, Jotai, Recoil, MobX, and Valtio. React does not handle routing. So the ecosystem produced React Router, TanStack Router, and Next.js. React does not manage forms natively. So the ecosystem produced React Hook Form, Formik, and Final Form. React does not animate. So the ecosystem produced Framer Motion and React Spring.
Each library solves a real problem. The question is who created the problem.
The browser has addEventListener for events, the
History
API for routing,
FormData
for form handling, the
Web
Animations API for animation, and
CustomEvent
for cross-component communication. These are not experimental features behind
a flag. They are shipping standards, implemented in every major browser, with
years of stability behind them.
React’s ecosystem does not extend the platform. It replaces the platform. And the replacement costs 142 KB before it begins to function, plus whatever the ecosystem libraries add on top.
The jQuery Parallel
The industry has been here before. In 2006, jQuery arrived and solved genuine
problems: inconsistent DOM APIs across browsers, missing selector engines,
unreliable event handling. It dominated for a decade. Then the browsers
caught up. querySelector replaced Sizzle.
addEventListener replaced $.on().
fetch replaced $.ajax(). The gap closed.
jQuery became unnecessary.
Not overnight. Not dramatically. Gradually, and then completely. W3Techs still reports jQuery on 77% of websites, but the trajectory is unambiguous and the direction is down. New projects do not start with jQuery. The library persists because legacy persists, not because the need persists.
React’s trajectory is structurally identical. It solved real problems in
2013: the DOM API was verbose, state synchronisation was manual, and component
encapsulation required discipline the platform did not enforce. In 2026, the
browser offers querySelector, Proxy, Web Components,
fetch,
AbortController,
MutationObserver,
and the Web Animations API. The gap is closing. The same gap, the same direction,
the same conclusion.
The only question is the timeline. jQuery took roughly a decade. React may take longer, its integration into build tooling, hiring pipelines, and university curricula creates a structural inertia that jQuery never achieved. But inertia is not necessity. And “We have always used React” is not an architectural argument. It is a habit dressed in a tech stack.
Where React Earns Its Keep
This is not, to be tediously clear, a call to abolish React. The framework solves genuine problems for genuine applications. Google Docs. Figma. Spotify’s web player. Dense, stateful, interactive surfaces where users remain for hours and the framework cost amortises across thousands of interactions. Where client-side state management is not a convenience but a structural necessity. Where the Virtual DOM’s reconciliation model earns its overhead by managing complexity that would be unmanageable without it.
These applications exist. They represent, at a generous estimate, perhaps 5% of what gets built with React. The remaining 95% are marketing sites, blogs, documentation portals, product catalogues, and landing pages, documents with the occasional interactive element, served to browsers that handle documents natively and have done so for three decades.
The Invoice
What you pay for React: 142 KB of framework. A build pipeline (JSX, Babel,
Webpack/Vite, Node.js). A node_modules directory with 1,200
folders. A hydration phase that rebuilds what the server already rendered.
A Virtual DOM that diffs what the real DOM already knows. An ecosystem of
libraries that replaces what the browser already provides.
What the browser provides for free:
querySelector,
addEventListener,
classList,
fetch,
WebSocket,
CustomEvent,
FormData,
AbortController,
MutationObserver,
Proxy,
Web Components,
the Web Animations API,
and the History API.
All native. All without a build step. All shipping in every browser since
at least 2020.
React “Hello World” loads 142 KB before your code. Browser “Hello World” loads your code.
The fastest framework is the one you do not ship. And the most reliable dependency is the one that shipped with the browser.