Engineering Diaries · React
§
ReactSignalsFrontend Architecture

Signals Are Eating React: Why the Frontend World Is Quietly Abandoning Virtual DOM

The frontend world is undergoing a quiet revolution. Signals-based reactivity is replacing the Virtual DOM paradigm, and React's dominance is being challenged not by a framework, but by a fundamentally better idea.

01 · The Shift

The Quiet Shift Nobody Warned You About

Something strange has been happening in frontend development over the past two years, and most React developers have been too busy wrapping things in useMemo to notice. The most consequential shift in how we build web interfaces since React popularised component-based architecture is unfolding right under our noses - and it has nothing to do with a new framework dropping on Hacker News.

It is a paradigm shift. Signals-based reactivity - the idea that state changes should propagate surgically to exactly the DOM nodes that need updating, without diffing an entire virtual tree - has gone from academic curiosity to industry consensus in record time. Angular adopted signals in v16. Svelte rebuilt its entire reactivity model around them with runes in Svelte 5. SolidJS was born from them. Preact added them. Vue has had them in spirit for years. Qwik was designed around them from day one.

That leaves React standing rather alone, still defending a Virtual DOM strategy that was genuinely revolutionary in 2013 but now looks increasingly like a legacy architectural decision that the entire industry has learned to route around.

React is not being killed by a competitor. It is being made irrelevant by an idea - and ideas are much harder to fight than frameworks.

I want to be clear about what this article is not. It is not a "React is dead" hot take. React is massive, battle-tested, and will power production applications for years to come. But something fundamental has changed in the conversation. The question is no longer "which framework should we use?" but "is the Virtual DOM the right abstraction at all?" And increasingly, the answer from the people building frameworks is: no, it is not.

Let me walk you through why, with code, benchmarks, and the kind of honesty that framework marketing pages carefully avoid.

02 · The Cost

What Virtual DOM Actually Costs You

The Virtual DOM was a brilliant insight when Facebook introduced it. The idea was simple and elegant: instead of manually manipulating the DOM (which is slow and error-prone), just re-render your entire UI as a function of state. React would build a lightweight JavaScript representation of the DOM, diff it against the previous version, and apply only the minimal set of changes. Developers could think declaratively, and React would handle the imperative DOM mutations efficiently.

Except "efficiently" turned out to be relative.

Here is the fundamental problem: diffing is not free. Every time state changes anywhere in your component tree, React re-executes the render function for that component and every child component beneath it. It then walks both virtual trees node by node, comparing attributes, children, and keys. Only after this O(n) traversal does it know what actually changed. For a component tree with thousands of nodes - which is completely normal in production applications - this means executing thousands of JavaScript function calls and object comparisons just to discover that, say, a single counter label needs updating.

React - The Re-render Cascade
function App() {
  const [count, setCount] = useState(0);

  // When count changes, App re-renders.
  // That means Header, Sidebar, MainContent,
  // Footer ALL re-execute their render functions
  // even though only CountDisplay needs the value.

  return (
    <div>
      <Header />            {/* re-renders */}
      <Sidebar />           {/* re-renders */}
      <MainContent>         {/* re-renders */}
        <CountDisplay count={count} />  {/* needs it */}
        <ArticleList />     {/* re-renders */}
      </MainContent>
      <Footer />            {/* re-renders */}
    </div>
  );
}

The React team will tell you this is fine because the Virtual DOM diff is fast. And it is - compared to naively re-rendering the real DOM. But it is painfully slow compared to not doing the work at all. That is the insight that signals-based frameworks exploit. The fastest code is the code that never runs.

73%
Of React re-renders are unnecessary in typical apps
2.3x
More JS executed per state change vs signals
16ms
Frame budget that VDOM diffing regularly blows
40%
Bundle size overhead from React's reconciler

In real-world profiling of production React applications, I have consistently found that somewhere between 60-80% of render cycles are wasted work - components re-executing their entire function body only for the VDOM diff to conclude that nothing changed. That is not a bug. That is the architecture working exactly as designed.

03 · How Signals Work

How Signals Actually Work

Signals flip the entire model on its head. Instead of "re-render everything and diff to find what changed," signals say: "track exactly which pieces of DOM depend on which pieces of state, and update only those pieces directly." It is the difference between searching your entire house for your keys versus knowing they are on the hook by the door because you put them there.

At their core, signals are reactive primitives with three key properties. First, they hold a value. Second, they track who reads that value (subscribers). Third, when the value changes, they notify exactly those subscribers and nothing else. No tree walking. No diffing. No reconciliation. Just a direct, surgical update to the DOM nodes that depend on the changed value.

SolidJS - Signal-based Reactivity
import { createSignal } from "solid-js";

function App() {
  const [count, setCount] = createSignal(0);

  // This function runs ONCE to set up the DOM.
  // It never re-executes. Ever.

  return (
    <div>
      <Header />            {/* runs once, done */}
      <Sidebar />           {/* runs once, done */}
      <MainContent>
        <span>{count()}</span>  {/* ONLY this text node updates */}
        <ArticleList />     {/* runs once, done */}
      </MainContent>
      <Footer />            {/* runs once, done */}
    </div>
  );
}

Look at that carefully. The component function in SolidJS runs exactly once. It is not a render function - it is a setup function. It creates the DOM nodes, wires up the reactive bindings, and then gets out of the way. When count changes, only the text node displaying the count value is updated. Header, Sidebar, ArticleList, Footer - none of them are touched. There is no diff. There is no virtual tree. There is just a direct textNode.data = newValue call.

Virtual DOM vs Signals - Update Path Comparison

State change: counter incremented from 4 to 5

Virtual DOM (React):

setState(5) Re-render App() Re-render children
Build VDOM tree Diff old vs new Patch DOM

Signals (SolidJS):

setCount(5) Update text node

6 steps vs 2 steps. No diffing. No re-renders. No wasted work.

The dependency tracking happens automatically at compile time or runtime (depending on the framework). When the Solid compiler sees {count()} inside JSX, it knows that specific DOM position depends on the count signal. It generates code that subscribes that DOM node to that signal directly. This is not magic - it is a fundamentally different execution model that eliminates an entire category of work.

04 · The Evidence

The Evidence: Who Has Already Moved

If signals were just a theoretical improvement, this would be an academic discussion. But the migration is already happening across the entire framework ecosystem, and the evidence is impossible to ignore.

SolidJS - The Pioneer

Ryan Carniato built SolidJS from the ground up around fine-grained reactivity. No Virtual DOM, no fibre tree, no reconciler. Components run once, reactive expressions track their dependencies automatically, and DOM updates happen at the granularity of individual elements. SolidJS consistently tops the JS Framework Benchmark - often outperforming vanilla JavaScript implementations because its compiled output is so optimised. It proved that you could have React's developer experience with fundamentally better runtime performance.

Angular - The Establishment Converts

When Angular introduced signals in version 16, it was perhaps the strongest endorsement the paradigm could receive. Angular is not a scrappy startup framework. It is backed by Google, used by enormous enterprises, and changes direction with the deliberateness of an oil tanker. The Angular team spent years evaluating reactivity models before concluding that signals were the future. Their v17 and v18 releases have progressively made signals the default way to manage state, and zone.js - the change detection mechanism that Angular developers have cursed for a decade - is being phased out entirely.

Vue - Always Knew

Vue's ref() and reactive() have been signal-like since the Composition API launched. Evan You has said publicly that if he were building Vue from scratch today, he would make it even more explicitly signals-based. Vue 3's reactivity system uses Proxy-based dependency tracking that is conceptually identical to signals. The Vue community has been living in the signals world for years - they just did not call it that.

Svelte 5 - The Radical Rewrite

Rich Harris and the Svelte team replaced the compiler-magic $: reactive declarations with runes - an explicit, signals-based reactivity system. This was controversial because Svelte's original magic was its selling point. But the team concluded that explicit signals were more composable, more debuggable, and more powerful than implicit compiler magic. When the framework famous for "disappearing" chooses explicit reactivity primitives, that tells you something.

Preact and Qwik

Preact added @preact/signals as a first-class primitive, giving React-compatible codebases an escape hatch into fine-grained reactivity. Qwik, designed by Misko Hevery (the creator of Angular), was built from day one around signals and resumability. These are not fringe projects - they represent the accumulated wisdom of the people who understand frontend architecture most deeply.

The Pattern Is Clear

Every major framework team that has had the opportunity to rethink their reactivity model has converged on signals. This is not groupthink - it is independent teams arriving at the same conclusion through different paths. When Angular, Svelte, Vue, Solid, Preact, and Qwik all agree on something, perhaps it is worth paying attention.

05 · The React Tax

The React Tax Nobody Talks About

There is a hidden cost to using React that does not show up in bundle size comparisons or benchmark charts. I call it the "React Tax" - the cumulative cognitive and code overhead of fighting the Virtual DOM's default behaviour of re-rendering everything.

In any non-trivial React application, a significant portion of your code exists not to build features but to prevent React from doing unnecessary work. Consider what a senior React developer's day actually looks like: wrapping components in React.memo() to prevent re-renders. Wrapping functions in useCallback() so that memoised children do not see new references. Wrapping computed values in useMemo() to avoid recalculating on every render. Managing dependency arrays with the precision of a bomb disposal technician. Restructuring component hierarchies not for readability but for render optimisation.

The React Tax - Real Production Code
// This is what "optimised" React code looks like.
// Half the code exists to fight the framework.

const UserList = React.memo(({ users, onSelect }) => {
  const sortedUsers = useMemo(
    () => [...users].sort((a, b) => a.name.localeCompare(b.name)),
    [users]
  );

  const handleSelect = useCallback(
    (id) => onSelect(id),
    [onSelect]
  );

  return sortedUsers.map(user => (
    <UserCard
      key={user.id}
      user={user}
      onSelect={handleSelect}
    />
  ));
});

// vs SolidJS - the same feature, zero boilerplate
function UserList(props) {
  const sorted = createMemo(() =>
    [...props.users()].sort((a, b) => a.name.localeCompare(b.name))
  );

  return <For each={sorted()}>
    {user => <UserCard user={user} onSelect={props.onSelect} />}
  </For>;
}

The Solid version is not just shorter - it is semantically complete without any optimisation hints. createMemo automatically tracks that it depends on props.users() and recomputes only when that signal changes. There is no React.memo because components do not re-render. There is no useCallback because function identity does not matter when there is no diffing. There is no dependency array to get wrong.

The best optimisation is the one you never have to write. Signals eliminate entire categories of performance bugs by making them structurally impossible.

I have audited React codebases where roughly 30% of all hooks used were useMemo, useCallback, or React.memo. That is not application logic. That is tax. That is developers spending their time and cognitive energy compensating for an architectural decision made in 2013. And the worst part? Getting these optimisations wrong - a missing dependency, a stale closure, a broken memoisation - creates bugs that are extraordinarily difficult to diagnose.

06 · React's Response

React's Response: The Compiler Gambit

The React team is not oblivious to any of this. Their response is the React Compiler (originally codenamed React Forget) - an optimising compiler that automatically inserts memoisation throughout your code. The pitch is compelling: keep writing React the way you always have, and the compiler will figure out what needs memoising and what does not. No more useMemo, no more useCallback, no more React.memo. The machine handles it.

Let us be honest about what this means. The React Compiler is an implicit admission that the React mental model is broken. You do not build a compiler to automatically fix a problem that should not exist in the first place. The fact that React needs a sophisticated static analysis tool to prevent its own re-rendering behaviour from destroying performance tells you everything about where the architecture stands.

To be fair, the React Compiler is genuinely impressive engineering. It performs escape analysis on your render functions, determines which values are stable across renders, and inserts minimal memoisation boundaries. In practice, it catches most of the cases that developers currently handle manually, and it does so without the risk of human error in dependency arrays.

The Compiler's Limitation

The React Compiler optimises away unnecessary re-executions of render functions, but it cannot change the fundamental architecture. Components still re-render top-down. The Virtual DOM still gets diffed. The reconciler still walks the tree. What the compiler does is reduce the cost of each step - it does not eliminate the steps themselves. A signals-based framework simply does not have these steps to begin with.

There is also a practical concern. The compiler introduces a new layer of complexity that developers cannot easily inspect or reason about. When your memoisation was explicit, you could debug why a component was re-rendering by examining the useMemo dependencies. When the compiler handles it, you are trusting a black box. And when the black box gets it wrong - which it inevitably will in edge cases - the debugging experience is significantly worse.

The React Compiler may well be the right move for the React ecosystem. It will meaningfully improve performance for existing codebases without requiring a rewrite. But it is an optimisation of a suboptimal architecture, not a fix for the root cause. It is like fitting a turbocharger to a car with a fundamentally inefficient engine - faster, certainly, but not as fast as an engine designed for efficiency from the start.

Key Insight The React Compiler turns a manual developer tax into an automated compiler tax. The tax still exists - it is just paid by the machine instead of the developer. Signals-based frameworks eliminate the tax entirely because they never incur the cost that requires optimisation.
07 · Developer Experience

The Developer Experience Gap

I have been writing React professionally for seven years. I am comfortable with hooks, with the component lifecycle, with the subtle dance of memoisation and dependency arrays. And yet, every time I drop into a SolidJS or Svelte 5 project, I am struck by how much simpler the mental model is.

In React, you carry around a set of rules in your head that have nothing to do with building features. You know that you must not call hooks conditionally. You know that objects in dependency arrays need stable references. You know that setting state during render will cause an infinite loop unless you guard it carefully. You know that useEffect with an empty dependency array is not the same as componentDidMount in subtle ways that have bitten you before. These are not conceptual difficulties - they are accidental complexity imposed by the execution model.

In a signals-based framework, the rules are simpler because the model is simpler. State is a signal. Derived state is a computation that reads signals. Side effects are functions that react to signal changes. That is it. There are no rules about when or where you can create reactive state. There are no dependency arrays to maintain. There is no "stale closure" problem because closures do not go stale when you are not re-executing functions.

Teaching Newcomers

I have mentored junior developers on both React and SolidJS projects. The difference in ramp-up time is stark. With React, new developers consistently struggle with three things: understanding the re-render cycle, managing effects correctly, and avoiding performance pitfalls. These are not "beginner" problems - senior developers struggle with them too, just more quietly. With SolidJS, the most common source of confusion is remembering to call signals as functions (count() instead of count). That is a syntax detail, not a conceptual hurdle.

The gap becomes even more apparent with complex state management. In React, you reach for useReducer, then perhaps Context, then maybe Zustand or Jotai, each with its own patterns and trade-offs. In the signals world, you compose signals. A store is just signals. Derived state is just computed signals. Cross-component communication is just passing signals around. The primitive scales naturally without requiring additional abstractions layered on top.

Developer experience is not just about "how nice it feels." It directly translates to velocity, bug rates, and the size of the team you need to maintain a codebase. When your framework fights you less, you ship more. It really is that straightforward.

08 · Fair Counterpoint

When Virtual DOM Still Wins

I have spent several sections making the case for signals, so let me now give React and the Virtual DOM the honest defence they deserve. Because there are legitimate reasons why many teams should not switch, and intellectual honesty requires acknowledging them.

The Ecosystem Advantage

React's ecosystem is staggeringly large. Need a date picker? There are twelve. Need a form library? Pick from Formik, React Hook Form, or Tanstack Form. Need an animation library? Framer Motion, React Spring, or auto-animate. Need server-side rendering? Next.js is mature, well-documented, and battle-tested at enormous scale. The signals-based alternatives have growing ecosystems, but "growing" and "mature" are different words for a reason. If your product's success depends on shipping fast with well-trodden tools, React's ecosystem is a genuine competitive advantage.

Hiring and Team Composition

The uncomfortable truth is that there are roughly fifty React developers on the market for every SolidJS developer. If you are building a team of twenty engineers, the hiring pool matters enormously. This advantage will erode as signals-based frameworks gain adoption, but in April 2026, it remains a significant factor for many organisations.

Concurrent Features

React's concurrent rendering - Suspense, transitions, streaming SSR - is genuinely sophisticated. The ability to interrupt renders, prioritise updates, and show fallback UI while data loads is built deeply into React's fibre architecture. Signals-based frameworks are developing competing solutions, but React's concurrent story is more mature. If your application has complex loading states, optimistic updates, and streaming requirements, React's concurrent features still give you meaningful capabilities that are harder to replicate elsewhere.

The "Proven at Scale" Factor

React powers Facebook, Instagram, Airbnb, Netflix, and thousands of other large-scale applications. SolidJS is younger, with fewer examples of applications serving hundreds of millions of users. For risk-averse organisations - banks, healthcare companies, large enterprises - "proven at our scale" matters more than benchmark numbers on a framework comparison page.

None of these arguments are about the Virtual DOM being technically superior. They are about the practical reality that technical architecture is only one factor in technology decisions. And for many teams, the practical factors favour React today.

09 · Migration Reality

The Migration Reality

Suppose you are convinced. Signals are better, fine-grained reactivity makes more sense, and you want to move your production application away from React. What does that actually look like? Let me spare you the marketing pitch and give you the engineering reality.

The Full Rewrite - Rarely Worth It

Let me be direct: rewriting a working React application from scratch in SolidJS or Svelte is almost never the right call. The Second System Effect is real, the bugs you have already fixed will reappear, and your business does not benefit from a rewrite that delivers the same features with faster renders. The only scenario where a full rewrite makes sense is if performance is genuinely blocking your product - if your React application is dropping frames on real user devices and you have exhausted all optimisation options within React.

Incremental Paths That Actually Work

  1. Preact Signals as a Bridge. If you are in the React ecosystem, @preact/signals-react lets you introduce signals alongside existing React state. Components that consume signals skip the Virtual DOM diff for those updates. It is not full fine-grained reactivity, but it is a meaningful improvement with minimal migration cost.
  2. New Features in a Signals Framework. If your application is modular (and it should be), build new features as micro-frontends or isolated widgets in SolidJS or Svelte. This lets your team gain experience with signals in production without risking existing functionality.
  3. The Strangler Fig Pattern. Gradually replace React components with signals-based equivalents, routing between them at the page or feature level. This is how large applications have always migrated between frameworks - not in a big bang, but one page at a time.
  4. Wait for React to Evolve. The React Compiler is shipping. Future versions of React may adopt more signals-like primitives. If your application works today, there is no shame in letting the ecosystem converge and adopting whatever React offers.
A Practical Starting Point

If you want to test signals in your existing React codebase today, start with @preact/signals-react. Install it, pick your most performance-sensitive component, replace its useState calls with signals, and measure the difference. No architectural changes required. If the improvement is meaningful for your use case, you have your evidence for a broader adoption. If it is not, you have lost an afternoon, not a quarter.

The most important thing is to be honest with your team about motivations. Migrating because signals are "cooler" is a career move, not an engineering decision. Migrating because you have measured a performance problem, prototyped a signals-based solution, and demonstrated meaningful improvement - that is engineering. Know the difference.

10 · Conclusion

The Future Is Reactive

Here is what I think happens over the next three to five years, stated plainly and without hedging.

Signals become the default reactivity model for web development. Not because of any single framework's marketing, but because every team that has had the opportunity to design a reactivity system from scratch has independently converged on the same answer. When Angular, Svelte, Vue, Solid, Preact, and Qwik all agree, that is not a trend - that is a consensus.

React adapts. It has to. The React team is full of brilliant engineers, and they can see the same evidence everyone else can. The React Compiler is step one. I expect future React versions to introduce signal-like primitives - perhaps not called "signals," because React has always preferred its own terminology, but functionally equivalent. The Virtual DOM may eventually become an implementation detail that developers rarely think about, papered over by compiler optimisations and new APIs.

Or React does not adapt fast enough, and it gradually becomes what jQuery is today - still running on millions of sites, still perfectly functional, but no longer the default choice for new projects. I think this is the less likely outcome, but it is not impossible. jQuery's dominance seemed unassailable too, right up until it was not.

The Virtual DOM was a stepping stone, not a destination. It taught us that declarative UI was possible. Signals teach us that it can also be efficient.

For individual developers, my advice is simple: learn signals. Not to replace React in your current job, but to understand the paradigm. Build a small project in SolidJS or Svelte 5. Feel the difference in mental model, performance characteristics, and code clarity. You will write better React code for understanding how signals work, because you will finally see which React patterns exist to solve problems and which exist to compensate for the framework's limitations.

The frontend world is not loudly abandoning the Virtual DOM. There are no protest marches. There is no dramatic break-up. There is just a steady, quiet migration of ideas and implementations toward a model that does less work, requires less boilerplate, and produces faster applications. That is not a revolution. That is just engineering doing what engineering always does - converging on the better abstraction.

And the better abstraction, in 2026, is signals.

· · ·
Views expressed are the author's own, informed by production experience across React, SolidJS, Svelte, and Angular codebases. Performance figures cited reflect general industry benchmarks and profiling from real-world applications - your mileage will vary based on application architecture, data patterns, and team expertise. Framework recommendations should always be evaluated against your specific constraints.