// A genealogy of modern frontend development
The Frontend Framework
Family Tree
React didn't just ship a library — it shipped a mental model. Every major framework since has inherited that model, then diverged on how to implement it. Understanding the genealogy makes the landscape make sense.
React solved a decade-old web problem in 2013 with a conceptual flip: instead of writing step-by-step instructions telling the browser how to change the page, you describe what the page should look like given the current data — and let the framework handle the updates. Every major framework since adopted that model completely.
They diverged not on the goal, but on the mechanism. Vue uses reactive proxies to track dependencies, Angular bundles a full enterprise toolkit, Svelte compiles the reactivity away entirely at build time, and Solid tracks fine-grained dependencies without any tree-diffing at all. The industry is now converging on "signals" as the right reactivity primitive — with React as the notable holdout, betting on its compiler instead.
⚡ Why This Matters
The "React vs Vue vs Svelte" debate generates more heat than light, because it usually skips the genealogy. These frameworks share a common ancestor and a common mental model. Once you understand what they all inherited — and why each one chose to change what it did — the entire landscape stops looking like competing opinions and starts looking like a single evolving conversation about one hard problem.
01 / The Old World
Before React, the DOM was in charge
To understand why React mattered, you first need to understand what everyone was doing before it — and what was slowly breaking.
Pre-React frontend code was fundamentally imperative: you wrote instructions telling the browser step-by-step how to change the page. User clicks a button? You reach into the DOM and change a specific element by hand. Data loads from a server? You find the relevant elements and update their text. The page itself was the source of truth for what the user saw.
This works fine for small pages. It breaks down badly as complexity grows — because now your JavaScript data and your HTML page can drift out of sync with each other, and keeping them aligned is entirely your problem. Historically, every framework had a different answer to this synchronisation problem. None of them were quite right.
💡 Mental Model — Imperative vs. Declarative
Imperative: "Go to the fridge. Take out the milk. Pour it in the glass." You describe every step.
Declarative: "I want a glass of milk." You describe the desired outcome and let someone else figure out the steps.
The entire shift in frontend development from the pre-React era to today is essentially this: from imperative to declarative. From "here's how to update the DOM" to "here's what the DOM should look like right now."
jQuery (2006)
Not a framework — a cross-browser utility library. jQuery's original job was
normalizing wildly inconsistent browser APIs so that $('#button').click()
worked the same in every browser. It was the right tool for its time. But scaling
jQuery apps meant spaghetti event handlers — dozens of scattered
click, hover, and submit listeners that each read and mutated the same DOM nodes in
slightly different ways — alongside DOM elements doubling as the app's only state store,
and no clear answer to "what is the current state of my app right now?"
AngularJS (2010)
Google's attempt at adding structure. Introduced two-way data binding: a live, bidirectional connection between a JavaScript variable and a UI element. Think of a spreadsheet where cell A1 is linked to an input field on a form — type in the field and A1 updates; change A1 and the field updates. Neither side owns the data; both sides stay in sync automatically. This was magic when it worked. The problem was its detection mechanism: a "dirty-checking" digest cycle that re-evaluated the entire application state on every user interaction to find what had changed. For large apps, this was slow and unpredictable.
Backbone & Ember
Backbone gave you models and events but left most wiring to you — highly flexible, highly boilerplate-heavy. Ember went the opposite direction: opinionated, convention- driven, with answers for everything. Both still struggled at the core with keeping the DOM in sync with your data as both changed over time. The synchronisation problem had no clean solution yet.
The shared problem was synchronisation. How do you keep what the user sees consistent with your application data as both change over time? Everyone had a different answer. None of them were right.
02 / The Trunk
React's core insight: UI = f(state)
When React launched in 2013 (built by Facebook, now Meta), it reframed the problem entirely. Instead of asking "how do I update the DOM when something changes?", it asked: "what should the DOM look like right now, given this data?"
This is expressed in the shorthand UI = f(state) — the UI is a pure function of your state. Give the framework your current data, and it figures out what the page should look like. The DOM becomes a reflection of your data, not a separate thing you manage by hand.
To make this practical, React introduced the virtual DOM: a lightweight JavaScript representation of what the real browser DOM should look like. When your state changes, React builds a new virtual copy, compares it to the previous one to find the differences (a process called diffing — comparing two snapshots node-by-node to find exactly what changed), and applies only those minimal changes to the real DOM.
💡 Mental Model — The Virtual DOM
Think of it like a rough draft. When you want to revise a document, you don't reprint the whole thing with every edit — you mark up a draft, compare it to the published version, and only reprint the changed sections. React's virtual DOM is the rough draft. Applying it to the real browser DOM is the reprinting — and it only reprints what changed.
Direct DOM manipulation is expensive (browsers have to recalculate layout, repaint pixels). The virtual DOM is cheap (it's just JavaScript objects in memory). By batching and minimising real DOM writes, React made the declarative model fast enough to use in production.
But here's the important distinction: the virtual DOM was React's mechanism — the specific implementation choice it made to bring the declarative model to life. It was never the idea itself. The idea was the mental model. And that mental model, expressed as five core principles, was adopted wholesale by every framework that came after.
Pillar 01
Component model
UI is built from composable, reusable pieces. A button, a card, a nav bar — all defined once and assembled together. Props (data) flow in; events flow out. Now universal across every framework in existence.
Pillar 02
Declarative rendering
Describe what the output should look like for a given state, not the imperative steps to get there. The framework diffs the desired output against the current state and handles the updates.
Pillar 03
State as source of truth
The DOM is a reflection of state, not a store of state. Your data lives in JavaScript. The page is just a view of it. This inversion — simple as it sounds — changes how you reason about everything.
Pillar 04
Co-location
Template, logic, and often styles live together in one file — not split by file type across a folder structure. This makes components self-contained and easier to reason about in isolation.
Pillar 05
Unidirectional data flow
Data travels one direction: parent to child via props. Children communicate back via callbacks and events. Even frameworks that offer two-way binding (like Vue's v-model or Angular's ngModel) implement it as syntactic sugar over this underlying pattern — shorthand that the framework expands into the longer one-way-flow code before running; more convenient to write, but doing the same thing underneath. Not a replacement for it.
03 / The Branches
How each framework diverged
Every framework that emerged after React inherited the five pillars above without debate. Nobody went back to two-way binding as a primary model. Nobody returned to imperative DOM mutation. The React thesis — declarative rendering, state as truth, component model, one-way flow — was simply correct, and the community recognised it within a few years.
But the mental model and the mechanism are two different things. The frameworks that followed diverged precisely on the mechanism: not on what to achieve, but on how to achieve it. And from one central question — "what's the right way to keep the DOM in sync with state?" — several genuinely different answers emerged, each with real tradeoffs.
There are three fault lines worth understanding before looking at each framework individually. Each framework is a coherent set of choices along these three axes — not a random collection of features.
Fault Line 01
How to track what changed
Runtime diffing (React): a library ships in the browser that builds a virtual DOM tree on every render, diffs it against the previous version, and applies only the delta to the real DOM. Flexible, but you're paying for the diff on every update.
Compile-time (Svelte): when you run npm run build,
Svelte's compiler reads your component files and generates targeted DOM update code
— vanilla JavaScript that directly updates only the exact DOM nodes that depend on each
variable. No library to ship at runtime, because the heavy lifting happened before the
browser ever loaded the page.
Fine-grained signals (Solid, Vue 3, Angular 16+): instead of diffing a whole tree, you track which specific variables each piece of the UI reads. When a variable updates, only the DOM nodes that read it update — no tree, no diff. We'll cover signals in full in Section 04.
Fault Line 02
Where the UI logic lives
JS-first (React, Solid): markup lives inside JavaScript files using
JSX syntax. Conditionals are ternaries (JavaScript's compact inline
if/else: isLoggedIn ? <Dashboard /> : <Login />), loops are
.map(), dynamic class names are built with template literals
(backtick strings that can embed expressions directly:
`btn btn--{variant}`). Everything is a JavaScript expression —
the full power of the language is available anywhere.
HTML-first (Vue, Angular, Svelte): HTML is the primary authoring
format, with special attributes adding logic to what looks like markup:
v-if (Vue), *ngFor (Angular),
{#if} (Svelte). More readable for designers and HTML-native
developers; the logic is slightly less flexible than full JS expressions.
Neither is categorically better. JS-first is more powerful for highly dynamic UI. HTML-first is often more approachable and self-documenting.
Fault Line 03
Library vs. framework — how much is decided for you
A library solves one specific problem well and expects you to assemble the rest. React is a rendering library — it handles UI and that's it. You choose your own router, data-fetching solution, state management, form library, and testing setup. This gives you extraordinary flexibility, but every React project makes architectural decisions that a more opinionated tool would have already made for you.
A framework makes those decisions for you. Angular is the clearest example: it ships with a router, an HTTP client, a forms module, a dependency injection container, and strong conventions for project structure. You work within its opinions. This reduces decision fatigue and makes large teams more consistent, at the cost of flexibility and a steeper initial learning curve.
Vue sits in between — it calls itself "progressive." You can drop it into a page with a single script tag and use it just to handle one component, or you can build a full app with its official router and state library. Svelte and Solid lean library, with official meta-frameworks (SvelteKit, SolidStart) available if you need more.
Framework Profiles
Inherited from React
Where it diverged — and how it works
Vue uses reactive proxies rather than virtual DOM diffing as its primary
reactivity primitive. A JavaScript Proxy is an object that wraps another
object and intercepts every read and write. When a component renders, Vue wraps your
data in a Proxy and takes note of which properties the component reads. Later, when
any of those properties change, Vue knows exactly which components need to re-render —
and only those components.
This is more precise than React's full-subtree diffing: Vue only re-renders components that directly depend on changed data. It still uses a virtual DOM layer above that for the actual DOM reconciliation, making it a hybrid approach — more targeted than React, but not as fine-grained as Solid.
The "progressive" design was deliberate: Vue can be added to an existing page via a CDN script tag without any build step, making it uniquely accessible to developers coming from non-SPA backgrounds.
Inherited from React
Where it diverged — and how it works
Angular 2+ is a complete rewrite and bears almost no resemblance to AngularJS. It adopted TypeScript as a first-class requirement and expanded scope dramatically in the opposite direction from React — a full, batteries-included framework with a built-in router, HTTP client, forms module, and a dependency injection (DI) container (a system where classes declare what services they need and the framework provides them automatically, rather than each class constructing its own dependencies). It's the framework where you don't make architectural decisions — Angular has already made them.
Change detection has historically worked through Zone.js, a library
that monkey-patches the browser's async APIs — it replaces the native
versions of setTimeout, addEventListener, fetch,
and similar functions with wrapped versions that do the original job plus notify Angular
when they complete. (Monkey-patching means swapping out a function at runtime so that
callers get the replacement without knowing it happened.) When any async operation
completes, Zone.js notifies Angular, which then checks the entire
component tree to see if any data has changed. It's effective but heavy: you're
paying to check everything, every time, whether or not your component was involved.
Angular 16 introduced first-class signal() support, providing a more
surgical escape hatch from Zone.js for performance-critical components.
⚠ Watch Out — Angular ≠ AngularJS
"Angular" (2016+) and "AngularJS" (2010–2022 EOL) are different frameworks by different teams with different architectures. If you encounter a tutorial, Stack Overflow answer, or job listing that says "Angular" without a version number, check the date. Anything before 2016 is describing AngularJS — a legacy framework no longer under active development.
Inherited from React
Where it diverged — and how it works
Svelte's bet was the most radical of the post-React generation: eliminate the runtime entirely. Instead of shipping a library that diffs virtual DOM trees at runtime, Svelte's compiler analyses your components at build time and emits vanilla JavaScript that surgically updates the DOM. No virtual DOM, no reactive proxy — just precise, targeted DOM operations.
The mechanism is elegant: Svelte instruments your variable assignments. In a normal
JavaScript file, count++ just increments a number. In a Svelte component,
that same statement compiles into code that increments the number
and updates every DOM node that displays it. The developer writes simple code;
the compiler generates the complex part.
The runtime shipped to the browser is tiny because all the analysis and wiring happened
at build time. Svelte 5 introduced "runes" — $state, $derived,
$effect — making the reactive primitives explicit rather than implicitly
inferred by the compiler, and bringing Svelte's mental model closer to the signals
consensus (covered in Section 04).
Inherited from React
Where it diverged — and how it works
Solid looks like React on the surface but works completely differently underneath. The key difference: components in Solid are functions that run exactly once. In React, a component function re-runs every time its state changes (triggering a re-render and virtual DOM diff). In Solid, the component function runs once to set up the DOM, then never runs again.
Instead of re-rendering, Solid uses fine-grained signals — reactive primitives that track which specific DOM expressions read which signals. When a signal updates, only the exact DOM nodes that depend on that signal update. No component re-rendering, no virtual DOM, no diffing of any kind.
The result is React's developer ergonomics (JSX, familiar patterns) combined with performance characteristics closer to Svelte. The two goals — familiar API and minimal runtime overhead — don't have to conflict. They just require abandoning the virtual DOM to achieve both at once.
04 / The Current Moment
Everyone is converging on signals
The most important story in frontend right now isn't divergence — it's convergence. Vue 3, Angular 16+, Svelte 5, and Solid have all independently concluded that fine-grained reactive signals are the right foundation for reactivity. They arrived via completely different paths. They landed on the same primitive.
💡 Mental Model — What Is a Signal?
A signal is the simplest possible reactive primitive: a piece of state with a getter and a setter, where anything that reads the value during a computation automatically subscribes to future updates.
Think of a spreadsheet. Cell A1 contains a number. Cells B1 and C1 have formulas that reference A1. When A1 changes, only B1 and C1 recalculate — not the whole spreadsheet, not unrelated cells, only the cells with formulas that actually read A1. Signals work exactly like this: change a signal, and only the UI expressions that read that signal update.
This is more surgical than React's approach (re-render the whole component and diff the output) and more explicit than Svelte's original approach (infer reactivity from assignment syntax). It's the primitive that the industry has settled on as the right level of abstraction.
The signals consensus
Each framework arrived by a different path. Each landed on the same primitive.
Vue 3
ref() / reactive()
Angular 16+
signal()
Svelte 5
$state rune
Solid
createSignal()
React is now the outlier. It still uses virtual DOM diffing — when state changes, it
re-renders the component and its subtree, then diffs the result against the previous virtual
DOM. Its answer to the performance cost is the React Compiler (formerly
called "React Forget"): a build-time tool that automatically inserts
memoisation — caching a computation's result the first time it runs and
returning that cached result on subsequent calls as long as the inputs haven't changed —
that you'd otherwise write by hand with useMemo and useCallback.
Rather than tracking dependencies more precisely (the signals approach), React's compiler
makes the existing approach cheaper by skipping re-renders when inputs haven't changed.
It's a different path to the same destination — doing less work per update — and it has the significant advantage of requiring no changes to how you write React code. Whether it closes the performance gap with signal-based frameworks in real-world applications is an active debate.
React exported the mental model. The ecosystem is refining the mechanism. The mental model won. The virtual DOM was always just a detail.
05 / Reference
Frameworks at a glance
Every framework in this table shares the five React pillars: component model, declarative rendering, state as source of truth, co-location, and unidirectional data flow. The differences are all in the "how."
| Framework | Reactivity model | Template style | Binding | Scope |
|---|---|---|---|---|
| React | Virtual DOM diffing | JSX (JS-first) | One-way | Library |
| Vue | Reactive proxy + VDOM | HTML templates (.vue) | v-model (2-way sugar) | Progressive |
| Angular | Zone.js + Signals (v16+) | HTML templates (.html) | ngModel (2-way sugar) | Full framework |
| Svelte | Compile-time (Runes v5) | HTML-first (.svelte) | bind: (2-way sugar) | Library |
| Solid.js | Fine-grained signals | JSX (JS-first) | One-way | Library |
Key Terms Glossary
Virtual DOM
A lightweight JavaScript copy of the real browser DOM. React builds a new virtual copy on every render, diffs it against the previous version, and applies only the changes. The diff is cheap; direct DOM manipulation is expensive — this minimises the expensive part.
Reactive Proxy
A JavaScript Proxy object that wraps your data and intercepts every read and write. Vue uses this to track which components depend on which data. When data changes, Vue knows exactly which components to re-render — and only those.
Signal
A reactive value (getter + setter) where anything that reads it during a computation automatically subscribes to updates. When the value changes, only those specific computations re-run. Think spreadsheet cells: change A1, and only cells with formulas referencing A1 recalculate.
Zone.js
A library that wraps the browser's async APIs (setTimeout, fetch, addEventListener, etc.) to intercept when async work finishes. Angular uses it to know when to check the component tree for changes. Effective, but catches everything — even async work unrelated to your UI.
Compile-time reactivity
Reactivity that's set up by the build tool before the browser loads the page, rather than by a runtime library. Svelte analyses your component files at build time and emits targeted DOM-update code. Smaller bundles, no diffing overhead — but the compiler must understand your code statically.
Dependency Injection (DI)
A pattern (central to Angular) where a class declares what services it needs rather than constructing them itself. Angular's DI container reads those declarations and provides the right instances automatically. Makes large codebases easier to test and maintain — at the cost of an extra layer of abstraction to learn.
💡 The One Mental Model to Keep
UI = f(state). Your UI is a pure function of your data. Give the framework the current data, and it figures out what the page should look like. All five frameworks implement this. They differ only in how efficiently they compute the result when data changes.
What comes next
Natural T3 continuations of this topic — not live yet, but they're coming.
Build Tools Explained
Vite, esbuild, webpack, Rollup: what they actually do, why Vite won the DX war, and what it means for your project setup.
TypeScript for JavaScript Developers
Types, inference, generics — why every major framework standardized on it and how to read unfamiliar TS without freezing.
React Server Components Deep Dive
What RSCs change about data fetching, bundle size, and where your code runs — and when NOT to use them.