Svelte 5 with runes — six months in: the honest verdict
Runes are the best reactivity API in the JS ecosystem today for teams that can live with a smaller pool than React. Two real traps before you migrate.
By Ethan
1,654 words · 9 min read
The runes system is the best reactivity API in the JavaScript ecosystem right now. That statement would have been provocation six months ago. Six months into Svelte 5’s stable life, it holds. The caveats are real but specific: TypeScript-heavy codebases hit a verbosity wall on prop typing, and $state cannot cross the SvelteKit server/client boundary the way teams expect. Know those two traps before you start the migration and you will be fine.
Who this is for
Developers running Svelte 4 in production and deciding whether to migrate, or frontend engineers evaluating frameworks for a new project where React’s ecosystem size is not a hard requirement.
What runes actually are
You have read the announcement post. You do not need the tutorial here. What matters is how the mental model shift feels once you live in it.
Svelte 4 reactivity was compiler magic: declare a variable with let, mutate it, and the compiler tracked it. It worked until it did not. Nested objects, cross-file state, and reactive class instances all had surprising edges. The fixes were a grab-bag of escape hatches — $: for derived state, writable stores for anything shared, get/set imports to read a store outside a component.
Runes replace that grab-bag with seven explicit primitives. The five you will use daily:
| Rune | What it replaces |
|---|---|
$state | let for mutable reactive vars |
$derived | $: computed values |
$effect | $: with side effects |
$props | export let for component props |
$bindable | two-way-bound export let |
The same reactive counter in Svelte 4 versus 5:
<!-- Svelte 4 -->
<script>
let count = 0;
$: doubled = count * 2;
</script>
<button on:click={() => count++}>{count} → {doubled}</button>
<!-- Svelte 5 -->
<script>
let count = $state(0);
const doubled = $derived(count * 2);
</script>
<button onclick={() => count++}>{count} → {doubled}</button>
The Svelte 5 version is more lines. That is the point. The reactivity is explicit. You can read a Svelte 5 component and know exactly what is reactive without mentally running the compiler. When you open a Svelte 4 component you inherited, you often cannot say that.
$state on arrays and objects uses a proxy, so deep mutations are tracked: items.push(x) is reactive. In Svelte 4, you needed items = [...items, x] to trigger an update. That single improvement removes an entire class of subtle bugs.
$effect is the most dangerous primitive. It re-runs whenever its dependencies change. If you mutate state inside it, you get an infinite loop:
<script>
let items = $state([]);
let filtered = $state([]);
// Infinite loop — $effect sees filtered change, re-runs, changes filtered again
$effect(() => {
filtered = items.filter(i => i.active);
});
</script>
The fix: use $derived for anything that is a pure transformation of state. Reserve $effect for genuine side effects — DOM manipulation, analytics calls, third-party library integration. If you can replace an $effect with $derived, do it.
The migration: honest assessment
The automated tool — npx sv migrate svelte-5 — handles roughly 80% of the work. In a 30,000-line Svelte 4 codebase, the script took 40 minutes of automated changes and about two days of manual fixups. That ratio is more favorable than most framework migrations.
What the tool handles: let → $state, export let → $props, simple $: → $derived or $effect, event directive renaming (on:click → onclick).
What requires manual attention:
createEventDispatcher — replaced by callback props. The script flags usages but cannot auto-convert them without knowing your component’s API contract:
<!-- Svelte 4 -->
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
</script>
<button on:click={() => dispatch('save', data)}>Save</button>
<!-- Svelte 5 -->
<script>
let { onsave } = $props();
</script>
<button onclick={() => onsave(data)}>Save</button>
beforeUpdate/afterUpdate — these lifecycle hooks are gone. You reconstruct their behavior with $effect and cleanup functions, which is usually an improvement, but requires thought per usage.
Complex $: chains — the migration script converts obvious cases. Multi-step reactive chains with conditionals need a human to decide whether the intent was derived state, a side effect, or a guard.
The TypeScript trap — prop typing in Svelte 5 is more verbose than export let. This is the one that will slow you down if your codebase is TypeScript-heavy:
<!-- Svelte 4: concise -->
<script lang="ts">
export let title: string;
export let count = 0;
</script>
<!-- Svelte 5: explicit interface required -->
<script lang="ts">
interface Props {
title: string;
count?: number;
}
let { title, count = 0 }: Props = $props();
</script>
For components with many props, this adds up. A component library with 40 components will cost a full day of interface additions that the migration script cannot do for you. The result is more readable, but the migration is not free.
The load function trap — $state cannot be returned from SvelteKit load functions. Reactive state does not cross the server/client boundary:
// +page.ts
export function load() {
// Does not work — $state is component-local
return { items: $state([]) }; // ❌
// Return plain data; assign to $state inside the component
return { items: [] }; // ✅
}
If you need reactivity from load output, you still need stores. The Svelte 5 documentation does not make this obvious. Stores are not deprecated for that pattern — runes are not a drop-in replacement in every context.
Performance: what the benchmarks mean (and don’t mean)
Svelte 5 leads the krausest/js-framework-benchmark across DOM-operation tests and ships a meaningfully smaller runtime than React or Vue — measured differences, not marketing copy.
What those numbers mean in practice: in a typical 50–200 component application, the DOM performance difference is invisible. The benchmark gap translates to a handful of milliseconds in a 200ms frame budget. You will not see it on real hardware under real workloads.
Where the numbers actually matter:
- Large list UIs rendering thousands of rows simultaneously — the gap is real at that scale
- Bundle-sensitive deployments — Svelte 5’s smaller runtime matters on slow mobile connections, where every KB under 3G conditions hits PageSpeed scores
- Embedded widgets — a component loaded on third-party pages benefits from a smaller runtime footprint
For an internal dashboard or a SaaS product with authenticated users on broadband, the performance difference between Svelte 5 and React is not your bottleneck. Team familiarity, component library availability, and hiring pipeline are.
Ecosystem check: is it safe out there?
The late 2025 ecosystem status:
- shadcn-svelte — v1.0 shipped June 2024, now at v1.2+; production stable with solid component coverage
- Ark UI — accessible primitives library, Svelte 5 support shipped
- Storybook 9 — Svelte 5 components work; runes and snippets supported in the CSF story format
- VS Code extension — runes-aware
- eslint-plugin-svelte — current, full runes support
The IDE story is no longer a concern. That was the question six months ago. It is not now.
The New York Times uses Svelte in production — a credible signal that large engineering orgs have found it stable enough for serious products.
The ecosystem gap versus React is real and not closing fast. 2.7M weekly npm downloads versus React’s ~190M means a narrower component library selection and a smaller “google this error” surface. If you need a production-ready virtualized table with twelve configuration options, you will find it in the React ecosystem before you find it in Svelte’s. For a side-by-side breakdown of the trade-offs, see React vs Svelte.
State of JS 2024: Svelte tops the rankings in overall positive opinions among front-end frameworks. Satisfaction metrics, not usage metrics, but they tell you that teams who choose Svelte tend to stay.
Hosting: Vercel’s SvelteKit integration is the current reference deployment target — official partner, zero-config deploys, edge function support. Netlify works equally well. Neither has an ecosystem advantage that would influence a framework decision. If Next.js is also on your shortlist, SvelteKit vs Next.js covers the routing and deployment differences in depth.
When Svelte 5 is the right call — and when it isn’t
Pick Svelte 5 if:
- You are starting a new project and React’s ecosystem breadth is not a hard requirement
- Your team is small (1–5 engineers) where framework familiarity is quick to establish
- Bundle size on slow mobile connections is a meaningful product concern
- You are building a library or embeddable widget where runtime footprint affects third parties
Stay on React if:
- Hiring is a near-term concern — React’s developer pool substantially dwarfs Svelte’s (State of JS 2025)
- You need a specific component (enterprise data grid, video SDK, chart library) that only ships React bindings
- Your existing codebase is React and there is no concrete problem that migration would solve
- RSC-first SSR with complex server data fetching is your core product — React Server Components and Next.js 16 have a meaningful ecosystem lead here
If you are on Svelte 4: migrate. The tool handles most of it. The runes model is strictly better than the implicit reactivity of Svelte 4, and the two traps — TypeScript props verbosity and load function stores — are knowable and survivable. The longer you wait, the more the Svelte ecosystem diverges from your codebase.
References
- Svelte 5 is alive — official stable release announcement, October 22, 2024
- Runes — original runes RFC and motivation
- Svelte 5 migration guide — official migration reference
- What are runes? — API reference for all seven primitives
- Svelte 5 runes in practice — real-world usage notes from a production migration
- Svelte 5 stores revisited — when stores are still the right tool
- Mixed signals with Svelte 5 — honest community retrospective
- Svelte discussion #13277 — community migration experiences and edge cases
- State of JS 2024 — retention and satisfaction data