
Why I switched to Svelte 5 and runes
A month after launch, I explain why runes won me over faster than I expected.
I’ve spent a month migrating projects to Svelte 5, and I’m going to say something I didn’t expect to say this soon: runes won me over before I even finished the first migrated project. Coming from years of defending Svelte 3 and 4’s implicit reactivity as one of its best selling points, I didn’t expect this from myself.
What was wrong with $: that I didn’t want to admit
Reactivity with $: worked fine while the component was small. As it grew, it stopped being obvious why a block ran or didn’t run — it depended on which variables Svelte managed to “guess” by statically analyzing the code, and that magic, when it failed, failed silently. More than once I lost half an hour looking for why a value wasn’t updating, only to discover that Svelte hadn’t detected the dependency because it was hidden inside a function.
What changes with runes
$state, $derived and $effect make explicit what used to be implicit. A reactive variable is now declared as such, instead of “being reactive because it lives at the root of a .svelte component.” A derived value is declared as derived, instead of trying to get the compiler to guess that a given $: computes something rather than running a side effect. You no longer have to memorize the specific cases where Svelte manages to track the dependency and where it doesn’t — if something depends on a piece of state, you say so, and that’s it.
Migrating wasn’t as traumatic as I feared
I expected a long, painful migration. In practice, starting with $state on loose variables and leaving the rest of the component untouched worked without issues — you can migrate component by component, without having to halt the whole project for weeks. The only thing that made me review more carefully was components with a lot of state shared across several files, where I used to use svelte/store stores and now prefer classes with runes at the module level.
What I still miss
Honestly, not much. Maybe the brevity of writing $: total = price * quantity on a single line, versus let total = $derived(price * quantity), which is a handful of characters longer. It’s a minor complaint compared to what you gain in being able to read a component top to bottom and know exactly what’s state, what’s derived, and what’s a side effect, without having to guess anything.