Settling the Framework Debate: Why Svelte 5 Won

For fifteen years we’ve argued about frontend frameworks like it was a values question. Angular vs. React. React vs. Vue. Vue vs. Svelte. Svelte vs. Solid. Each round produced a new tribe, a new Hacker News thread, and a new pile of YouTube essays with the word “killer” in the title.
Then, somewhere around the Svelte 5 release in late 2024, the debate quietly stopped being a debate. Not because everyone agreed - they didn’t - but because the technical gap between the best option and the rest got wide enough that the argument shifted from “which framework is better” to “which constraints prevent us from using the obvious one.”
This is a consultancy talking, not a fan club. We ship for clients across embedded firmware, multi-tenant SaaS, real-time platforms, AI infrastructure, and ops dashboards. We’ve used React in anger for ten years, Vue for five, and Svelte 5 in production for eighteen months across seven projects. Here’s the case for why, when nothing prevents it, we now default to Svelte 5 - and why we believe the framework debate is effectively settled for new builds.
The shift no one announced
To understand what changed, you have to look past Svelte and see the wider quiet revolution: the entire frontend world has been converging on fine-grained signals.
- SolidJS built its identity on it from day one.
- Vue 3.4+ ships Vapor Mode, which discards the virtual DOM and runs on signals.
- Preact has signals as a first-party primitive.
- React introduced the React Compiler - a compile-time tool that retrofits manual memoization automatically, essentially trying to bolt fine-grained tracking onto a framework that fundamentally re-renders entire component subtrees.
- Angular introduced signals in v17 and is shedding zone.js across v18 and v19.
The industry consensus, by the end of 2025, was unambiguous: the way to do reactivity is fine-grained tracking of individual reactive primitives, with the compiler doing the hard work. Whether you call them signals (Solid, Vue, Preact, Angular), runes (Svelte), or compiler-rewritten useState (React), the destination is the same.
Svelte 5 didn’t invent this direction. It executed it the most completely.
Here’s why that matters.
What runes actually are
In Svelte 4, reactivity was a parser trick. The compiler scanned for let count = 0 and $: blocks and rewrote them. It worked beautifully right up until it didn’t - for example, the moment you tried to share state outside a .svelte file, or pass reactive values across module boundaries. The mental model split: identifiers behaved differently inside vs. outside reactive scope.
Svelte 5 replaced that with runes - explicit compiler primitives prefixed with $. $state, $derived, $effect, $props. They’re not functions in the JavaScript sense; the compiler recognizes them syntactically and rewrites the surrounding code to track reads and writes at the granularity of individual variables.
<script lang="ts">
let count = $state(0);
let double = $derived(count * 2);
let label = $derived.by(() => {
if (count === 0) return 'start';
return count < 10 ? 'low' : 'high';
});
$effect(() => {
console.log(`count is now ${count}`);
});
</script>
<button onclick={() => count++}>
{count} → {double} ({label})
</button> This looks like a syntactic refresh. It is in fact a complete architectural rewrite of how reactivity threads through a Svelte program. Three things make it qualitatively different from Svelte 4 and from every other framework’s primary reactive primitive:
1. Runes work everywhere, including outside .svelte files
In a .svelte.ts module, you can declare:
// $lib/stores/cart.svelte.ts
function createCart() {
let items = $state<CartItem[]>([]);
let total = $derived(items.reduce((s, i) => s + i.price * i.qty, 0));
return {
get items() { return items; },
get total() { return total; },
add(item: CartItem) { items = [...items, item]; },
clear() { items = []; }
};
}
export const cart = createCart(); That’s a fully reactive, type-safe, shared store - written as plain TypeScript, no writable() wrappers, no $store auto-subscription syntax, no React-Context-and-three-hooks boilerplate. The same primitives the components use. One reactive model, one mental model.
2. The compiler statically knows what depends on what
This is the part most discussions skip. Because runes are syntactic - recognized at parse time, not runtime - the Svelte compiler can build a static dependency graph of every reactive read and write in your component. It then emits direct DOM mutation code targeting exactly the nodes that depend on each piece of state.
There is no virtual DOM diff. There is no reconciliation pass. There is no “render the component again and figure out what changed.” When count updates, the compiler has already emitted: “increment this specific text node; call this specific class toggle.” That’s it.
SolidJS achieves the same result at runtime, with signal subscriptions. React with the React Compiler tries to approximate it via aggressive memoization. Svelte does it at compile time because the language itself was designed for it.
3. The runtime is small enough to disappear
A SvelteKit production build of a moderately complex page - code-split, hydrated, fully reactive - typically lands in the 5–15 KB gzipped range for the framework runtime. Compare:
| Framework | Hello-world runtime (gzipped, approx) |
|---|---|
| React 19 + ReactDOM | ~46 KB |
| Vue 3.5 | ~36 KB |
| SolidJS | ~7 KB |
| Svelte 5 | ~3–6 KB |
For consultancy work where Lighthouse scores translate directly into client revenue (eCommerce, marketing, anywhere mobile traffic dominates), that delta is not a rounding error. It’s a competitive moat.
The benchmarks, honestly
The community benchmark people pay attention to is Stefan Krause’s js-framework-benchmark, which measures DOM operations across every popular framework on a standardized table-rendering workload. The 2026 picture:
- Startup memory: Svelte 5 idles around 7.9 MB, Vue around 11.4 MB, React 19 around 18.7 MB. Solid hovers just below Svelte.
- Initial render of a 1,000-row table: Svelte ~110 ms, Vue ~142 ms, React ~178 ms (even with the React Compiler enabled). Solid neck-and-neck with Svelte.
- Row updates and swaps: Solid edges Svelte by single-digit percentages; both leave React and Vue behind by 20–40%.
- Bundle size on a real app: depending on the workload, Svelte ships 5–14× less JavaScript than the equivalent React build.
We will say the boring honest thing now: benchmarks are not your app. A 30% startup delta on a table renderer does not mean your dashboard will load 30% faster. The bottleneck on most real apps is network, fonts, third-party scripts, and your own data-fetching architecture - not the framework runtime.
But here’s the thing: the direction of the numbers is real, and it accumulates. Smaller bundles parse faster on mobile. Less memory pressure means smoother animations on low-end devices. Surgical DOM updates mean no jank on data-heavy dashboards. Across a full project lifecycle, the framework choice compounds into a 5–15% Lighthouse-score swing that you can feel.
Solid is technically the fastest framework in the benchmark. For 95% of applications the difference between Solid and Svelte is imperceptible, and Svelte wins on every axis that surrounds raw performance: SSR ergonomics, animation primitives, transition system, meta-framework (SvelteKit) maturity, ecosystem.
The compiler advantage that nobody talks about
Most “Svelte is fast” articles stop at bundle size. The deeper win is what the compiler can know about your code at build time.
Because every reactive primitive is syntactically explicit ($state, $derived, $effect, $props), the compiler can:
- Inline most derivations directly into the generated DOM update path. No subscription bookkeeping at runtime for trivial cases.
- Eliminate entire reactivity machinery for components that don’t have any state at all (a surprising number of UI components).
- Statically detect dead code paths - branches whose conditions cannot vary at runtime - and remove them.
- Validate prop types against component signatures at build time, with full TypeScript integration.
- Emit per-property update functions for object state, so updating
user.emaildoesn’t re-evaluate any derived value that only depends onuser.name.
This is the reason a Svelte app’s runtime stays tiny: a huge amount of work that React and Vue must do at runtime (the virtual DOM, the scheduler, the reconciler, the memoization layer) simply doesn’t exist in the compiled output. It happens at build time, on your machine, once. The user’s browser ships only the actual instructions to update specific DOM nodes.
Vue’s Vapor Mode chases the same prize. Angular’s signals + zone.js removal chases the same prize. The React Compiler chases the same prize. Svelte got there first and with less ceremony, because its compiler has always been the point.
The honest tradeoffs
If we were selling something, we’d stop here. Since we’re not, the genuine downsides:
TypeScript prop typing is more verbose than Svelte 4. The interface Props { ... } + let { foo, bar }: Props = $props() pattern is correct but it repeats names you’d previously have written once. After eighteen months we’re used to it; the first week it felt like a regression.
The .svelte.ts extension constraint is real. Runes only work in files Svelte’s compiler processes. If you want reactive state in a vanilla .ts module - say, a utility your backend also imports - you have to either rename it to .svelte.ts (which now requires the Svelte compiler to be in scope) or fall back to manual store patterns. We’ve hit this in code that crosses the SSR/server boundary. It’s manageable, but it leaks framework concerns into places where they don’t belong.
The library ecosystem is smaller. When a client says “we need a feature-complete data grid with virtualization, column resize, frozen rows, CSV export, and Excel-paste behavior, in five days,” there’s still exactly one ecosystem with that off-the-shelf - and it’s React. We’ve shipped wrappers around React components inside SvelteKit hosts more than once. It works; it’s not elegant.
Community migration from Svelte 4 is still in progress. Stack Overflow answers, tutorials, and library docs from 2023–2024 will quietly mislead you on the new APIs. Pin your reading to the official docs for now and treat anything older than 2024 as suspect.
Hiring. There are fewer senior Svelte engineers in the global pool than senior React engineers. By a lot. If your team plans to grow from 5 to 50 in twelve months and you need to hire fast, that’s a strategic constraint worth weighing.
None of this is a deal-breaker. All of it is real.
So when do we still reach for React?
Two specific situations:
- An existing React codebase with a mature design system. Rewriting to switch frameworks is a six-month tax we’re almost never allowed to pay. We extend what’s there.
- A specific third-party widget that ships React bindings only. Enterprise data grids, complex rich-text editors, some legacy auth SDKs. We wrap them, we don’t replace them.
That’s it. Two cases. Everywhere else, including the contact form on this exact page, the live globe in the hero, the case-study renderer, and the dynamic OG image pipeline - Svelte 5 + SvelteKit, top to bottom.
Why the debate is over (for new builds)
The framework wars were a useful argument when the contenders made fundamentally different tradeoffs: React’s component model vs. Vue’s templates vs. Angular’s opinions vs. Svelte’s compiler magic. Each had a strong claim somewhere.
The convergence on fine-grained signals collapses that. Every major framework now agrees on what reactivity should look like. The question is no longer which model is right - it’s which implementation gets there with the least ceremony, the smallest runtime, and the cleanest developer experience.
On that question, our consultancy’s verdict after eighteen months and seven production projects is: it’s Svelte 5. By a clear margin. SolidJS is the worthy second; Vue is a credible third; React remains the safest enterprise choice but at a tangible cost in bundle size, memory, and the conceptual overhead of bolting signals onto a framework that was designed around component-level re-renders.
If you’re starting a new product in 2026 and your team can hire for it, default to Svelte 5. If you’re maintaining a React codebase with five years of momentum and a working design system, do not rewrite it - make it as fast as you can with what you have, and revisit when the cost-benefit shifts.
That’s not zealotry. It’s just the math working out.
If you’re evaluating Svelte 5 for a real project, or you’re stuck on the question of whether a rewrite makes sense for your codebase, we’d like to hear about it. We’ve made the call both ways and we’re happy to share what we’ve learned.