Every Frontend Framework Is the Same Now (And That's a Good Thing)
I've built the same app in React, Vue, Svelte, Angular, and Solid. Same components, same reactivity, same lifecycle, same patterns—different syntax. After years of framework wars, the frontend world quietly converged on the same ideas. Here's what I learned by building the same dashboard five times.
The Argument That Started This Post
A colleague and I were debating frameworks over lunch. He was a Vue guy. I was a React guy. We’d had this argument before—reactivity models, template syntax, virtual DOM overhead. The usual trench warfare.
Then he showed me his latest Vue 3 component:
<script setup>
import { ref, onMounted, computed } from 'vue';
const users = ref([]);
const search = ref('');
const loading = ref(true);
const filteredUsers = computed(() =>
users.value.filter(u => u.name.toLowerCase().includes(search.value.toLowerCase()))
);
onMounted(async () => {
const res = await fetch('/api/users');
users.value = await res.json();
loading.value = false;
});
</script>
<template>
<input v-model="search" placeholder="Search users..." />
<div v-if="loading">Loading...</div>
<ul v-else>
<li v-for="user in filteredUsers" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>
I stared at it. Then I pulled up my React version:
import { useState, useEffect, useMemo } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [search, setSearch] = useState('');
const [loading, setLoading] = useState(true);
const filteredUsers = useMemo(() =>
users.filter(u => u.name.toLowerCase().includes(search.toLowerCase())),
[users, search]
);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => { setUsers(data); setLoading(false); });
}, []);
return (
<>
<input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search users..." />
{loading ? <div>Loading...</div> : (
<ul>
{filteredUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</>
);
}
We both went quiet for a second. Same state variables. Same computed/derived value. Same lifecycle hook for fetching. Same conditional rendering. Same list rendering with keys.
Different syntax. Identical ideas.
“We’ve been arguing about nothing,” he said.
He was right.
The Experiment: One App, Five Frameworks
That conversation wouldn’t leave my head. So I did something stupid: I built the same dashboard app in React, Vue, Svelte, Angular, and Solid. A user management dashboard with a list, search, filters, a form, and API integration. Real enough to matter.
Here’s what I found.
Components: Same Concept, Different Wrapper
Every framework structures UI as components. Self-contained, reusable, composable. The syntax is the only difference.
A Button Component
React:
function Button({ label, onClick, variant = 'primary' }) {
return (
<button className={`btn btn-${variant}`} onClick={onClick}>
{label}
</button>
);
}
Vue:
<script setup>
defineProps({
label: String,
variant: { type: String, default: 'primary' }
});
defineEmits(['click']);
</script>
<template>
<button :class="`btn btn-${variant}`" @click="$emit('click')">
{{ label }}
</button>
</template>
Svelte:
<script>
export let label;
export let variant = 'primary';
</script>
<button class="btn btn-{variant}" on:click>
{label}
</button>
Angular:
@Component({
selector: 'app-button',
template: `
<button [class]="'btn btn-' + variant" (click)="onClick.emit()">
{{ label }}
</button>
`
})
export class ButtonComponent {
@Input() label: string;
@Input() variant = 'primary';
@Output() onClick = new EventEmitter();
}
Solid:
function Button(props) {
return (
<button class={`btn btn-${props.variant ?? 'primary'}`} onClick={props.onClick}>
{props.label}
</button>
);
}
Five versions. They all do the same thing: accept props, emit events, render markup. The mental model is identical. If you understand one, you can read the other four in ten minutes.
Reactivity: Everyone Got There Eventually
This is where frameworks used to genuinely differ. React had setState. Angular had change detection. Vue had its reactivity system. Svelte compiled reactivity away.
But look at where they all ended up:
Declaring Reactive State
React: const [count, setCount] = useState(0)
Vue 3: const count = ref(0)
Svelte: let count = $state(0) (Svelte 5 runes) or just let count = 0 (Svelte 4)
Angular: count = signal(0) (Angular 16+)
Solid: const [count, setCount] = createSignal(0)
Look at that list. React has useState. Vue has ref. Angular has signal. Solid has createSignal. The naming is different but the concept is identical: a reactive primitive that holds a value and notifies the system when it changes.
Even the getter/setter pattern converged. React’s [value, setValue] tuple. Solid’s [getter, setter] tuple. Angular signals’ .set() and .update(). Vue’s .value. They’re all trying to solve the same problem: track a piece of state, update it, and re-render whatever depends on it.
Derived/Computed Values
React: const doubled = useMemo(() => count * 2, [count])
Vue: const doubled = computed(() => count.value * 2)
Svelte 5: const doubled = $derived(count * 2)
Angular: doubled = computed(() => this.count() * 2)
Solid: const doubled = () => count() * 2 (auto-tracked)
Same concept. A value that automatically updates when its dependencies change. Every framework has this now.
Side Effects
React: useEffect(() => { ... }, [deps])
Vue: watch(source, () => { ... })
Svelte 5: $effect(() => { ... })
Angular: effect(() => { ... })
Solid: createEffect(() => { ... })
The word “effect” is literally in every single one.
The Lifecycle Is Universal
Mount, update, cleanup. Every framework has the same lifecycle, just exposed differently:
| Concept | React | Vue | Svelte | Angular | Solid |
|---|---|---|---|---|---|
| On mount | useEffect(() => {}, []) | onMounted() | onMount() | ngOnInit() / afterNextRender() | onMount() |
| On update | useEffect(() => {}, [dep]) | watch() | $effect() | effect() | createEffect() |
| On cleanup | Return fn from useEffect | onUnmounted() | onDestroy() | ngOnDestroy() | onCleanup() |
When I built my dashboard in all five frameworks, I found myself making the same architectural decisions every time:
- Fetch data on mount
- Store it in reactive state
- Derive filtered/sorted views
- Handle loading and error states
- Clean up subscriptions on unmount
The framework didn’t influence my architecture. At all.
Conditional Rendering: Syntax Wars
This is where people get heated, but honestly, it’s just preference:
React (JavaScript expressions):
{isLoggedIn ? <Dashboard /> : <Login />}
{showSidebar && <Sidebar />}
Vue (directives):
<Dashboard v-if="isLoggedIn" />
<Login v-else />
<Sidebar v-show="showSidebar" />
Svelte (blocks):
{#if isLoggedIn}
<Dashboard />
{:else}
<Login />
{/if}
Angular (control flow):
@if (isLoggedIn) {
<app-dashboard />
} @else {
<app-login />
}
React uses JavaScript. Vue uses directives. Svelte uses blocks. Angular recently added a control flow syntax that looks like… JavaScript. They’re converging even in template syntax.
List Rendering: The Same Map
React:
{users.map(user => <UserCard key={user.id} user={user} />)}
Vue:
<UserCard v-for="user in users" :key="user.id" :user="user" />
Svelte:
{#each users as user (user.id)}
<UserCard {user} />
{/each}
Angular:
@for (user of users; track user.id) {
<app-user-card [user]="user" />
}
Every single one needs a unique key. Every single one iterates over an array. Every single one renders a component per item. The syntax is a skin over the same operation.
State Management: The Same Three Layers
After building the dashboard five times, I noticed every framework ecosystem has settled on the same three layers of state:
Layer 1: Local Component State
For state that belongs to one component. A form input value. A toggle. A dropdown open/closed.
Every framework has this built in. useState, ref, let, signal, createSignal.
Layer 2: Shared/Global State
For state that multiple components need. User authentication. Theme preference. Shopping cart.
React has Context + Zustand/Jotai. Vue has Pinia. Svelte has stores. Angular has services. Solid has context + stores.
The patterns are the same: define state outside components, subscribe to it inside components, update it from anywhere.
Layer 3: Server/Async State
For data from an API. Products. User profiles. Search results.
React has TanStack Query. Vue has TanStack Query (same library, different adapter). Svelte has TanStack Query. Angular has TanStack Query.
I’m not even joking. The same library powers async state management across four frameworks. That’s how converged the patterns are.
The Things That Actually Differ
I’d be dishonest if I said there were zero differences. There are a few that matter:
Bundle Size and Performance Model
Svelte and Solid compile away the framework. There’s no virtual DOM, no runtime diffing. The output is surgical DOM updates.
React and Vue use a virtual DOM. They diff a virtual tree and patch the real DOM.
Angular uses its own change detection (moving toward signals).
For most apps, this doesn’t matter. For high-frequency updates (real-time dashboards, animations, games), Solid and Svelte have a genuine edge.
TypeScript Integration
Angular was built with TypeScript. It’s not optional—it’s the language.
React has excellent TypeScript support, but it’s opt-in.
Vue 3 has great TypeScript support, a massive improvement over Vue 2.
Svelte’s TypeScript support has gotten much better but still feels slightly bolted-on in places.
Solid has strong TypeScript support from the ground up.
Server-Side Rendering Story
React has Next.js (and Server Components, a genuine innovation). Vue has Nuxt. Svelte has SvelteKit. Angular has Angular Universal. Solid has SolidStart.
Every framework has a meta-framework. Every meta-framework does SSR, SSG, file-based routing, and API routes. Even the meta-frameworks converged.
Ecosystem Size
React’s ecosystem is the largest by far. More libraries, more tutorials, more job postings, more Stack Overflow answers. This is a real practical advantage, not a technical one.
Why This Convergence Happened
I think three things drove it:
1. The component model won. In 2014, components were React’s radical idea. By 2024, every framework is component-based. Nobody’s going back to jQuery-style DOM manipulation. The debate is over.
2. Reactivity primitives won. Signals (or signal-like primitives) are in every framework now. React’s hooks were the inflection point, but Vue’s Composition API, Svelte’s runes, Angular’s signals, and Solid’s signals all converged on the same idea: fine-grained reactive state.
3. The web platform caught up. ES modules, fetch, async/await, template literals, destructuring—modern JavaScript has so many built-in features that frameworks need to do less. When the language handles the basics, frameworks converge on the parts that remain.
What This Means for You
If You’re Picking a Framework
Pick the one your team knows. Or the one with the most job postings in your area. Or the one whose syntax feels most natural. The technical differences are marginal.
Seriously. I’ve built production apps in React and Vue. The code quality depended on the developer, not the framework. A great Svelte app and a great React app are equally great. A terrible Angular app and a terrible Vue app are equally terrible.
If You’re Learning Your First Framework
Learn one well. Don’t framework-hop. The concepts transfer perfectly. When you deeply understand components, reactivity, effects, and state management in React, switching to Vue takes a week, not a month.
I’d recommend React purely for ecosystem size and job market, but if Vue or Svelte clicks better with your brain, go for it. You’ll learn the same underlying ideas.
If You’re an Experienced Developer
Stop arguing about frameworks. Start arguing about user experience, accessibility, performance, and architecture. Those are the decisions that actually affect your users. Nobody has ever said “this website is great because it uses Vue instead of React.”
What I Wish I’d Known Earlier
-
Frameworks are syntax, not architecture. The architecture of a well-built web app looks the same regardless of framework. Components, state, effects, routing, data fetching—the decisions are framework-agnostic.
-
Learn JavaScript deeply, not framework APIs. Closures, promises, the event loop, prototypes, modules—these transfer everywhere. Framework APIs change with every major version.
-
The “best” framework is the one you ship with. Analysis paralysis kills more projects than framework choice ever will.
-
Read other frameworks’ code. Even if you never use Vue, reading Vue source code will make you better at React. The ideas cross-pollinate.
-
The convergence will continue. In five years, the differences will be even smaller. Bet on web standards and JavaScript fundamentals. They outlast every framework.
The Framework Wars Are Over
And honestly? Everyone won.
React’s component model won. Vue’s reactivity system won. Svelte’s compilation approach won. Angular’s TypeScript-first approach won. They all adopted each other’s best ideas, and now we have five excellent ways to build the same thing.
The frontend community spent a decade arguing about which tool was best. Turns out, the tools were all listening to the arguments and becoming each other.
I still use React. My colleague still uses Vue. We stopped arguing about it. We talk about user experience now.
That feels like progress.
P.S. — I showed my five-framework dashboard to a designer on my team. She looked at all five versions, pixel-identical, and said: “I don’t care what you built it with. The loading state on the filter is janky. Fix that.” She was right. No framework fixes bad UX. That’s on us.
Saurav Sitaula
Software Architect • Nepal