Solid 2 is a MASSIVE Update (Goodbye SolidStart)

BBetter Stack
Computing/SoftwareInternet Technology

Transcript

00:00:00Solid 2 just shipped to release Canada, and this update is a big one.
00:00:03It completely reworks how async is handled in the framework, making it a first-class feature,
00:00:07getting you a way better developer experience for data fetching, loading states, and reactivity.
00:00:11Effects have been changed, stores have changed, boundaries have changed,
00:00:14and loads of SolidJSX syntax has changed, and if that wasn't enough,
00:00:17there's a new compiler toolchain written in Rust over 20 times faster than before,
00:00:21and the plugin now comes with a start mode, which completely kills the need for Solid Start.
00:00:25They actually released Solid Start 2 a couple of weeks ago,
00:00:27and in this update, you can say goodbye to it.
00:00:29This whole update actually feels like a real innovation in front-end frameworks,
00:00:32so let's jump in and see everything that changed.
00:00:39So the big headline in Solid 2 is that async is now part of the reactive graph.
00:00:44In Solid 1, you have to use the special create resource primitive to manage your async data,
00:00:48like we do with fetch user here, and from that resource,
00:00:50you could then access the latest resolve value, loading state, and errors.
00:00:54In Solid 2 though, this becomes much simpler.
00:00:56Computations can now just return promises directly, so we can use fetch user
00:00:59inside of create memo, and now user is simply a memo that happens to be async,
00:01:03and everything downstream understands it.
00:01:05Create resource has been removed.
00:01:07You may have also noticed that our suspense boundary got replaced with loading.
00:01:10They've actually reworked all of the boundaries,
00:01:12so suspense has now been removed, and you use a loading boundary,
00:01:15which renders the fallback if there's no data yet, so no first result,
00:01:18and then after that, you can use isPending if you want to track whether new values are being fetched.
00:01:22Error boundary also got replaced with erud,
00:01:24although this largely seems like a rename and a rework under the hood,
00:01:27and the same goes for suspense list, which has now been changed to reveal.
00:01:30Now those examples might make you think the async work is basically just renames,
00:01:34but under the hood, it's a complete different approach to handling async in frameworks.
00:01:37Ryan actually posted a diagram on Reddit to try and explain some of this,
00:01:40so I recommend reading that for a full deep dive,
00:01:42but the TLDR is most frameworks block UI with a suspense boundary at the same time they consume a
00:01:47promise, so use in React or await in Svelte, but Solid 2 consumes the promise on creation
00:01:52into its graph and blocks where the value is read.
00:01:54We can see this in React, where if I have all of my promises in my parent component and I click replay,
00:01:59I only ever see that first suspense boundary fallback as these requests are blocking the UI,
00:02:03and it waits for all of them to finish first.
00:02:05You can obviously solve this in other ways, like passing promises or co-locating,
00:02:09but it was always a trade-off between developer experience and user experience.
00:02:12Solid 1 did partially solve this already, I have all of my promises being consumed with
00:02:16create resource here, and when I click replay, the user profile shows when the data is read,
00:02:20and then we see the team skeleton until its data is ready, and the same goes for the member
00:02:24component. So the user experience part is solved because the UI immediately updates when the
00:02:28data is ready. The problem in Solid 1 though is we've had to scatter null and undefine checks
00:02:32everywhere when we're working with that value that we get from the resource. So Solid 2 fixes all of
00:02:36this, when we consume our promise with create memo, the promise goes into the graph immediately,
00:02:40and from that point on we only ever deal with a plain value, not the promise. So all of these
00:02:44nested components can be purely presentational, and the value also doesn't have the undefined
00:02:49state anymore. So we can remove all of those checks from our code, and at the same time we can use a
00:02:52loading boundary, and Solid will handle whether the value is ready or not. This gets you the best
00:02:56user experience, as UI shows when it's ready, and the code is cleaner so it's a better developer
00:03:00experience too. You could get a much better developer experience by subscribing, it keeps you up to date
00:03:04with AI and dev news like this. Now everything I've mentioned so far about async has been about
00:03:08reading, but what about writing? Because there's been some changes to how you do optimistic updates.
00:03:13In Solid 1, optimistic updates use the resource mutate function, but that's actually been removed and
00:03:18replaced with a much nicer flow with action, create optimistic, and create optimistic store.
00:03:23So if we wanted to have optimistic updates for our to-do app, first we create an optimistic store with
00:03:27our source of truth, which is going to be the server fetch, then we create an add to-do action,
00:03:31add the to-do to the local store instantly, fire off the save API, and then call refresh from the
00:03:35server when that's complete. Action actually gives the mutation a lifecycle, so the local changes we
00:03:40make to the optimistic store only exist for the lifetime of this action. That means if this API
00:03:44call were to error out, the optimistic override would disappear and the UI goes back to the
00:03:49authoritative value. So we now have automatic rollback, which is a pretty nice change from
00:03:52doing things manually in Solid 1. Now for those solid devs paying attention, you may have noticed
00:03:56something special about optimistic store. It actually handed us a draft here, which we mutated
00:04:01directly. This change has been made across all of the stores, so there's no need for the
00:04:04produce function anymore. That's been removed along with create mutable, as it's now just
00:04:08the default behavior. There is still a store path helper if you want the old pathway back
00:04:12though. Moving over to effects, they've made some big changes here as well. In Solid 1, an effect
00:04:17was one function that did everything, tracking whatever you read and also the side effect. Solid
00:04:212 splits this in two, separating dependency tracking from the side effect. The first function
00:04:26computes a value and gets tracked, and the second function is untracked, receives the value,
00:04:30and does all of the actual work. This creates a separation between what you depend on and what
00:04:34you do about it, and it replaces the need for the on function, as this is handled by the compute
00:04:39function now. And if you want the defer option that the on function had, you can simply do that as an
00:04:43option on the effect instead now. Alongside this, create computed was also removed, as you can simply
00:04:47use a memo if you want a derived value, or a split effect if you want a side effect. When it comes to
00:04:52cleaning up your effects, this also changed as well. Instead of calling on cleanup inside the function
00:04:56body, the apply function simply returns its cleanup function, kind of like React's approach. The same
00:05:01change has been made to the on mount function, which is now on settled. You return your cleanup
00:05:05from the callback. On settled actually fires when all of the async work under it has resolved,
00:05:10not just when the component mounts. When it comes to signals, not much has changed about how you use
00:05:14them, but the difference is under the hood. Solid 2 stages, writes, and commits them on the next
00:05:18micro task, so if you read a signal on the line right after setting it, you'll get the old value,
00:05:22but you can use the flush function to force the value through if you need it right away.
00:05:26This change has also meant that batch has been removed, because consecutive writes already share
00:05:30a batch by default, so there's nothing left to wrap. I think that's just about it for the async
00:05:34and reactivity changes that I wanted to cover, so let's take a look at JSX, which they also reworked,
00:05:39aiming to get a DOM model that's closer to HTML. If we start with classes, class list is now gone.
00:05:44Instead, class itself now takes strings, objects, or arrays. For conditionals now,
00:05:48you can simply use an object, so there's no more concatenation, filter, or join tricks.
00:05:52Attributes also got the same treatment, the attribute and bool prefixes are gone,
00:05:56you just write standard attributes, and booleans follow normal HTML presence and absence rules,
00:06:00so the attribute only appears if its value is truthy. On and on capture are also gone,
00:06:04just use the camel case event props now, and use a ref callback and add event listener if you need
00:06:08the native listener options. For directives, the username space is gone, it's been replaced by ref
00:06:13callbacks and directive factories, and ref now takes an array so you can stack multiple directives on
00:06:17the same element without wrapper function. Staying in JSX, lists also got simplified. In solid 1,
00:06:23you had two components for rendering an array, 4 and index. 4 was keyed so each item got matched by
00:06:28identity, and index was the opposite, it was mapped by position. In solid 2 though, index has been
00:06:32removed, so there's now just 4, but you can get that index behavior back by setting the prop keyed to
00:06:37false. And lastly for the JSX changes, there's also a new component called repeat. Instead of taking an array,
00:06:42this renders by count, you give it a start index and a count, and each row reads its own slot in the
00:06:47store, so updating one row touches nothing else. I think that is finally everything noteworthy from
00:06:52the framework point of view, so now let's focus on the tooling because the v plugin got two big changes,
00:06:57and the second one is why solid start doesn't need to exist anymore. First up, the compiler. Solid 2
00:07:01ships a brand new compiler toolchain written in Rust on top of Oxy, and the v plugin defaults to this,
00:07:06so you shouldn't have to make any changes. According to that benchmarks, they saw a speed up on
00:07:10our test project from around 440 milliseconds to compile down to just 19, and on a larger 1 megabyte
00:07:16single module, this came down from 25 seconds to just 70 milliseconds, so 355 times faster. The second
00:07:24change is the bigger one though. The plugin now has its own start mode. This gets you a full app build
00:07:28on plain v with no index HTML scripts or entry files needed. By default, this will be in client only
00:07:33mode, so building will emit purely static files, but you can also enable SSR if you want to ship a
00:07:38server bundle with the build and get server side rendering, and on that note, if you like server
00:07:42functions, use server is now a core feature. On top of that, you can also get file system routing
00:07:47with a router neutral package, giving you solid start proven conventions, hot module reloading, code
00:07:51splitting, and more. I really like this setup because deploying is either static files or a server module
00:07:56that exports a web standard fetch handler, which Cloudflare workers, Netlify, Nitro, Bun, and Dino
00:08:01already speak, so you deploy with your platform's own tooling, and there's no solid specific adapter in
00:08:06the middle. This does mean that solid start is no longer needed though. As that blog post says,
00:08:10solid start did its job. It was a meta framework meant to fill the gaps of solid, but now too brings all of
00:08:14those capabilities home. If you are running solid start, nothing changes today. It will continue to
00:08:18get maintenance updates, and you can check out that migration guide if you want to switch over.
00:08:22That's about as many updates as I can fit in one video. You can see this was a seriously big update
00:08:26with loads of changes and removals from the APIs, and there would have even been a few that I didn't
00:08:30cover, like start transition and use transition being removed, so I recommend checking out that
00:08:34migration guide from solid 1 to solid 2 if you want the full list, and it's also a great resource
00:08:39to point your agent at and ask it to migrate for you. I was going back and forth between solid
00:08:43one and solid 2 a lot, and Claude Code seemed to understand from this document and pick up all of
00:08:47the changes. I really liked diving deep into this update, and I'll be honest, I haven't used solid
00:08:51in a while, but solid 2 let me pick it up really easy, and I actually enjoyed using it quite a lot,
00:08:55so I think I might use it a bit more in the future. What do you think? Did you use solid before,
00:08:59and do you like this update? Let me know in the comments down below while you're there,
00:09:02subscribe, and as always see you in the next one.

Key Takeaway

Solid 2 reinvents frontend development by native async reactivity, a 355x faster Rust compiler, and a Vite start mode that retires Solid Start.

Highlights

  • Solid 2 integrates asynchronous operations directly into the reactive graph, removing the createResource primitive entirely.

  • A new Rust-based compiler toolchain achieves up to 355 times faster compilation speeds, reducing a 1-megabyte build from 25 seconds to 70 milliseconds.

  • The Vite plugin introduces a built-in start mode that completely eliminates the need for Solid Start.

  • Actions and optimistic stores provide automatic rollbacks for failed API calls, replacing manual state management.

  • Split effects separate dependency tracking from side effects by dividing the logic into a computing function and an untracked side-effect function.

Timeline

Async Integration in the Reactive Graph

  • Computations return promises directly within create memo.
  • Suspense and error boundaries are replaced with loading and erud boundaries.
  • Values are consumed immediately on creation to prevent UI blocking.

Solid 2 simplifies data fetching by removing createResource and allowing computations to handle promises natively. Downstream components receive plain values without undefined states. New loading boundaries manage fallback rendering automatically while maintaining an unblocked user interface.

Optimistic Updates and Effect Splitting

  • Actions and optimistic stores provide automatic rollbacks on API errors.
  • Effects are split into a tracked computation function and an untracked side-effect function.
  • Writes stage and commit on the next micro task, removing the need for manual batching.

Mutations receive a lifecycle through actions and create optimistic stores, allowing local changes to revert automatically if server requests fail. Effects separate dependency tracking from execution to clarify component dependencies. Furthermore, consecutive writes batch automatically by default.

JSX Simplifications and Tooling Improvements

  • JSX syntax aligns closer to standard HTML by removing class list, attribute prefixes, and index components.
  • A new Rust-based compiler toolchain dramatically accelerates build times.
  • The Vite plugin features a start mode that incorporates server-side rendering and file system routing natively.

JSX updates remove redundant syntax like class list and attribute prefixes, making component writing more natural. The framework introduces a Rust compiler toolchain that compiles large modules up to 355 times faster. Additionally, the Vite plugin absorbs Solid Start capabilities through built-in server-side rendering and use server features.

Community Posts

View all posts