Svelte 5 and the Future of Frameworks: A Chat with Rich Harris
smashingmagazine.com
smashingmagazine.com
What I like:
- Not a whole lot of magic syntax.
- The structure of files feels like I am building a backend app, its quite logical and easy to parse through.
- Easy to get up and running, I am sure the decisions exist but I struggle historically with all the sidecar products that can be added into the frontend framework.
- It combines the beauty of native JS/TS with svelte. I can hydrate a store with my own hand rolled api calls.
- There is not a whole lot going on from a API/user perspective. I have stores and routes which are built into the folder structure. I am sure there is more but that gets me to a useful MVP.
I'll add that my career isn't programming. However, Svelte let's me come back to my code base and understand everything, even after months. There is minimal framework related syntax to retain in my memory. Just have to know typescript well and that's it. Additionally, I don't need to retain all the rendering gotchas that other frameworks (eg, react) have. This has changed slightly with runes but I find them pretty easy to refresh on if I find im confused.
For me, this trade-off is well worth the limited availability of third party libraries. I wish someone would create a Laravel-like extension for Sveltekit. If I could just pull in user login flows, hashing, mail, etc all from organized Laravel-like docs, Sveltekit would be perfect for full-stack.
On the other hand, I've been trying out astro with svelte and it's been super nice, especially with the ability to have runes in other typescript files.
Astro is also built in top of Vite and the same thing happens there. If you reference `import.meta.env.SSR` it is statically replaced during build and unused code is tree-shaken out: https://vite.dev/guide/env-and-mode#env-variables
I do still believe that it may be good to point this out in the docs more thoroughly. In general though, couldn't there be some situations where using a universal load function like this may increase the chance for some security critical logic bugs?
Kit on the other hand has been a major pain. You won’t need a webserver oh wait you do! You have to make drastic tradeoffs when choosing between full SPA and SSR render only. Server side pre-rendering seems like middle ground nightmare that I just I don’t think I can tackle sufficiently right now. I need to get an MVP.
The (browser) issue is a symptom of this greater problem - it’s just not clear what runs on the server side and what doesn’t. The documentation is very poor on this and blogs are flat out wrong.
What's confusing about this and what could we do to help?
How do I serve svelte files using a python or golang backend and still have client side routing? These should have a fairly straightforward answer but I don't think they do.
By default, SvelteKit does SSR for the first page and client-side routing thereafter. This is fully configurable. Perhaps it's worth an additional mention on the routing page. I'll take a look later. Thanks for the suggestion.
I think this succinctly summarizes my gripes. The docs do generally make these assumptions, and are not clear when it's otherwise.
I like doing
<script lang="ts"> blah </script>
<style> blah blah </style>
<template> blah blah </template>
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/te...
I just wrote about my experience here on a project https://fzakaria.com/2025/01/28/bazel-build-event-protocol-v...
So, you will have the snippet where you want it to be rendered on the child:
```child.svelte
<script> let { myFirstSnippet, mySecondSnipper } = $props(); </script>
<div id="Your first snippet goes here"> {@render myFirstSnippet()} </div>
<div id="Your second snippet goes here"> {@render mySecondSnippet()} </div> ```
Here we are declaring that this child takes 2 snippets called `myFirstSnippet` and `mySecondSnippet` and we place them wherever they might go.
And then on the parent we need to actually build those snippets:
```parent.svelte
<script> import Child from ... </script>
{#snippet myFirstSnippet()} <span>Hello from firstSnippet!</span> {/snippet}
<Child {myFirstSnippet}>
{#snippet mySecondSnippet()} <span>Hello from secondSnippet!</span> {/snippet}
</Child> ```
Here we are taking that `Child` component and rendering the two snippets that will go into it, as you see, you can either pass the snippet as a named prop or you can declare it inside the child component with the same name as the prop the child gets.
The end result of this will be a `Child` component that's rendered on the `Parent` with the two snippets inside this child component.
I hope that clears things up.
Yes, what a completely frivolous change that will only cause headaches.
Changes like this are why I dislike the mentality in the JS ecosystem. It was just cosmetic, and creates unnecessary churn. They seem to be addicted to changing things. They didn't like the old syntax? Though. What's done is done.
Don't make frivolous changes in frameworks damn it.
A real world example: in Svelte 4, I had a custom input component that wrapped an <input> element. To be able to do `<MyInput on:change={callback} />` took a bunch of event forwarding boiler plate (or magic `on:change` syntax to forward that one event). When I wanted to also forward blur events I had to go through the same process. Now in Svelte 5 I can just use `const { foo, ...rest } = $props()` then spread with `<input {..rest}>` and I get all events forwarded for free.
They could have kept the original `on:change` syntax, but that would have made property access more painful (who wants to call `rest['on:change']`?) and would have broken all my other components that I haven't migrated yet.
The same goes for slots to snippets. Being able to pass snippets around like props has allowed me to simplify tons of components where slots were limiting.
As a solo dev working part time on a Javascript project, I definitely hate some of the ecosystem churn, but this is one refactor I'm happy to do, and I'd say the same about most of the Svelte 5 changes.
I love Rich Harris' work and appreciate what he gives to the community, but man he loves to rethink things.
Maybe someone here can shed light on something about runes that feels very off to me. The rune is on the wrong side of the equal sign? Like:
let counter = $state(0);
Looks like a reactive value assigned to a variable, but that’s not at all what’s going on. It’s actually a reactive variable initialized to 0. So, for example, despite what you’d expect you can’t even move the $state call into a utility function if you’d have some reason to. Very counterintuitive to me, and not really touched upon in the docs as far as I’ve seen, but I have to assume there’s some practical reason why they did it this way.I remember Svelte 2 (or 1? Can’t remember) had some weird things like this as well, where you’d think you could do something based on your experience from JavaScript but it just wasn’t supported. The label syntax in Svelte 3 was pretty brilliant because it was a clear indication that you shouldn’t expect normal JS rules to apply. It did have other problems that they appear to have fixed though.
const createCounter = () => $state(0);
let counter = createCounter();
> $state(...) can only be used as a variable declaration initializer or a class field
https://svelte.dev/e/state_invalid_placement (state_invalid_placement)It has to be part of a variable initialization even though it looks like it’s creating a value.
The Svelte 5 rollout feels like a frogmarch. The v3 docs and repls and tutorials are gone, not api versioned. The new docs are bad, IMO, conpared to the clear documentation 3 and 4 had for years, with constant blurring with SvelteKit, which is a server framework, no thanks. The new docs font appears designed to make one cease reading. The new syntax is ugly and verbose and vague, IMO, but admittedly i am having difficulty with the documentation, which may be the real issue. If there were a translation guide from how one does things in Svelte 3 to Svelte 5 it might feel less like learning a new framework. Well, nothing lasts forever.
There is a migration guide as well as a migration tool that will migrate most of your code for you. You can also see what an individual file looks like when migrated in the playground.
(For some background, I used to code SPAs from scratch with vanilla JS and jQuery in the 2000s, then switched to backbone.js then Angular, before picking up React that solved all the design issues of Angular)
You need to use a separate library for CSS with React. Svelte has the best way to deal with CSS built in. Which CSS library do you choose for your React project? There are so many libraries to consider, and so many bugs in each of those libraries.
You need to use a separate state management library with React. Svelte has state management built in. Which state management library do you choose for your React project? There are so many to consider, and so many bugs.
People think they want to use React because of the "ecosystem." But, they don't realize that React coerces you to use an ecosystem, an ecosystem that is full of buggy software and those bugs create impossible permutations of configuration issues and bugs (see Create React App). Bugs are not specific to React at all, all software has bugs. But, React forces you to use an ecosystem, and that's a bad thing. I write a lot of Svelte code with very few external libraries.
React does give you job security fixing all those bugs. I'll give you that. It is a wise career choice.
Svelte 5 is a big change. But, today, I refactored a bit of reactive code (which talks to a server and uses async code) inside a single template into an external shared component. That component has reactive code that can be shared across Svelte UI components. I could do that before with stores, but I had to be careful about how to use the reactivity. This new component isolates the reactivity in the right way and shares it in the right way. And, that share component is testable. It is an incredible experience when you get it.
Then it became “wait just don’t use hooks till we work out the issues.”
I mean you can just use CSS. Personally I use css modules. Not very buggy or react specific.
> a separate state management library
Yeah I do use tanstack query to manage server state. Does Svelte include something similar out of the box? Otherwise it’s 1) url/query param for app state 2) tanstack query for server state 3) hooks for reusable state or local component state. Not many library bugs with that approach and tbh I’ve never needed something like redux or zustand.
Problems galore in react, you should try out new js frameworks. Atleast vue and solid, to see what else is on the table
Though I agree trying other frameworks is a good practice. See what you're missing, or understand your preferred framework better.
If you are building an SPA there is no reason to choose something other than React. Many of these other frameworks focus on apps that aren't full-blown SPAs. Htmx, deno fresh, astro, remix, next.js, these are all designed for users that don't want SPAs.
I think that's great, but often we conflate these use cases which cause mass confusion. Htmx is not great for highly dynamic "desktop class" web apps and the same goes for next.js.
Previously,class components were the primary way to manage state and lifecycle methods.
Lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount were heavily used.
Now, functional components with Hooks (useState, useEffect, etc.) are the best practice. There were several iterations of different practices in between.
Previously, props were passed through multiple layers of components (prop drilling was established practice).
Now Context is used. State management, event handlers, component structure, CSS-in-JS: pretty much everything has changed over time.
No - I used React in the past, but no longer use it. Maintenance is a head-ache. I am utterly certain that within the next 2 years, another "life-changing" React paradigm will come into effect - can even bet on it.
I always felt that reading svelte code (prior to svelte 5) required to understand a sort of "magic syntax" very well, which was off putting.
I don't really mind having to declare reactive variables as `$state()`, it makes it very explicit that we want the variable to be reactive. It seems this change has also allowed for a lot more powerful and reusable code so I'm all for it. I guess that people who were already in the ecosystem might have found that they had to learn a lot of new stuff but imo this is very much a case of "might be a bit painful for our current users but it'll make life easier for everyone else in the future".
There's a few things I am a bit conflicted with but it's probably due to myself not really knowing how to solve a determinate problem rather than a problem with the framework itself. It's a bit difficult to get help sometimes.
After being a skeptic of svelte 5, its fully captured me.
The tutorials on the website are excellent and that was all I needed to get started.
It might still be a bit big, but PRs welcome if folks want to try slimming it down to just the stuff that's new in Svelte 5.
Those things can have utility (mostly subjective), but it comes at a cost of circumventing basic web behavior just to ship some HTML, CSS, and JS to the browser in a novel way.
The browser env is the best it's ever been (seriously—I say that as an IE6 veteran). But if your motivations are beyond just delivering HTML, CSS, JS, and WASM to the browser, you can invent some serious footguns and are forced to rationalize it as "a better approach."
VDOM may be "antiquated" (I mean it's a nested object or linked list which are standard paradigms) but slow depends on what you're doing. I did a linked list for my own full-stack framework's [1] component library and it's quite snappy.
Assuming you don't mean JSX transform by "compiler", you really don't have to do either one. For my framework [1], state is directly bound to DOM elements. Dependencies are determined at run time. It's not slow.
And if I understand correctly, you're forming relationships between parents/children by setting extra properties on the DOM nodes themselves (e.g., node.mutraction_parent = <Some Other DOM Node>)?
You never need to explicitly assign tree relationships like parents. Here's the example code from the front page. In this case, `model.clicks` is a dependency.
const model = track({ clicks: 0 });
const app = (
<button onclick={ () => ++model.clicks }>
{ model.clicks } clicks
</button>
);
document.body.append(app);That's the idea, in a nutshell.
I'm definitely not the first one to come up with this idea, but I hadn't found another UI framework that worked exactly like how I wanted. Solid is kind of close, but not it. Vue has some of this as well, but it's a bit more kitchen-sink in its scope.
At very least, Virtual DOM will survive at WASM borders if nowhere else. There's still talk of making it a lot easier to bind the DOM across WASM borders, but even then overhead may always be a strange trade-off making it useful to just pass a full Virtual DOM object to JS code on the other side of the border and letting it diff/patch. Most of the WASM-based front ends I've seen are generally some form of Virtual DOM under the hood.
That said, yes, the possibilities in doing more "direct DOM manipulation" again and "Vanilla DOM" are exciting and it is a bit of flip from the brief period when major React fans thought Virtual DOM was the greatest. I've been seeing some cool results in my own "non-Virtual DOM" projects.
I use Svelte when I want static site generation. It's good for that.
I hate that it's all different from v4 to v5.