As Solid marks its 1.0 release, creator Ryan Carniato discusses the origins of the framework, the latest JavaScript innovations, and the need to keep pushing front-end JS performance.
One of the most interesting JavaScript frameworks gaining traction these days is SolidJS. Solid is interesting because it takes JSX (Reactβs templating language) in novel directions. It decorates JSX with a handful of reactive primitives. It is compiled (similar to Svelte). It layers on higher-order services (like a central store and events). And for good measure, it tosses in full-featured SSR (server-side rendering) and Suspense.
I talked to Solidβs creator, Ryan Carniato, about how he and the Solid team pulled this off, how Solid relates to industry developments, how innovative front-end features (like partial hydration, streaming SSR, and Suspense) are implemented, and what motivates him to keep pushing the limits of front-end JavaScript performance.
Matthew Tyson: Hey Ryan, thanks for taking the time to chat today. First question, Solid is a compiled framework, like Svelte. Were you inspired by Svelte to go the compiled route?
Ryan Carniato: No. Svelte didnβt go hardcore compiled until V3 in 2019. We already had this system and were crushing the benchmarks as of early 2018. Svelte has some cool ideas that take compilation further and I was aware of them in terms of some decisions but not the initial direction.
Tyson: I think the amount of innovation in front-end JavaScript is astounding. Do you have a sense of where we are in that evolution? Are we near a peak?
Carniato: I think that we have hit the limits of what we can do in terms of raw rendering performance and honestly it isnβt good enough. Thatβs why this area keeps evolving, we know we can do better. Itβs also why you see so much research into other places to get every last ounce of performance or to improve user experience.
Iβve been saying this for a couple years. This isnβt slowing down. There is technology being used in some frameworks and libraries that hasnβt been tapped in some of the more popular ones and the stuff continues to be worked on and improved. I understand the desire for things to level out and in some sense it has as JS frameworks reach more maturity, but the target is still moving.
So there is much more to be done.
Tyson: You have put a lot of research and thought into performance. Solid reflects that (as seen in the benchmarks), but Solid also strikes me as having a very clean dev experience. Is that a focus for the Solid team, or is that more of a happy side effect?
Carniato: Well, I started with Knockout because I liked the DX [developer experience]. I liked the control. But at the same I saw all the flaws that Reactβs early press highlighted. So I looked at how we could fix it. I didnβt go out of my way since the formula was there. I was more focused on proving the performance. But the characteristics that made Knockout strong, like composable primitives and declarative data patterns, just continued to have a positive effect on DX.
Instead of having to invent new abstractions I just kept on finding I had the tools already. This kept things simpler in a sense. So I guess both? I started from DX and wanted to land on performance and consolidated on DX.
Tyson: I feel that the way the framework takes a few reactive primitives and layers on top of them makes it easy to work with, mentally. Solid also delivers SSR (server-side rendering). That continues to be an area of expansion. I know itβs a technical challenge to pull off. Has isomorphic support been part of your vision from the beginning? What was your experience in doing it?
Carniato: Hah⦠not at all. I was very much on client rendering is faster than server rendering. And to be fair the way I was doing it, it was. When frameworks out there wait for async data to respond (like you found with Next, Nuxt, or Sapper) client rendering even can beat it at paint and TTI [time to interactive]. I was putting out things like the Realworld Demo and was content how Solid scored better than these server-rendered implementations. But this is only true on fast networks.
Over time people would see Solidβs performance and SSR became the most requested feature, not even close. Iβd only worked on interactive, behind log-in apps, so I had no experience here. Luckily I came across Marko, eBayβs framework, which had pretty much perfected the art of SSR. Solid and Marko have slightly different goals but both projects benefited from each other immensely.
For Solidβs benefit I was exposed to the best techniques of doing SSR. It required rewiring my thinking. But I embraced it figuring out how to compile to the most optimal raw rendering, as well as incorporating streaming SSR. The latter wasnβt too hard given that I already had Suspense, so I just allowed it on the server. It was around that time it became clear to me what React has been up to all these years (and what is coming in React 18 next year). But I was able to get this into Solid last summer. And itβs amazing. This is the future.
Tyson: I need to research Marko.
Carniato: Yeah, people donβt know Marko but when it comes to multi-page apps and server rendering it has been deploying techniques at eBay production levels for the better part of a decade. Other frameworks are just starting to add features that theyβve had for years. Like streaming SSR and partial hydration.
Tyson: Can you describe how hydration works, and why partial hydration is an improvement?
Carniato: Itβs the process of running the components on the client for the first time in the browser to get them set up for future updates. We donβt render anything but it generally follows the same top-down execution. So partial hydration is about not shipping all that component code. Less JavaScript to the browser improves page load and interactivity speeds.
Unfortunately you can only really leverage partial hydration in certain types of apps. Our typical single-page apps donβt really get to leverage this as eventually everything needs to be in the browser. It amounts to lazy loading. But in multi-page apps true partial hydration means never sending that JavaScript.
Partial hydration might be a bit off topic, since it requires a different application architecture to leverage and almost no frameworks today optimize for this case. We are just starting to see things like Astro trying their hand at it.
Tyson: Thanks for describing that. Would you mind just describing streaming SSR in a brief way?
Carniato: OK, youβre asking all the tricky questions!
Streaming SSR is based around the idea that we can render as we fetch. If youβve ever developed a page with async loading in the browser you tend to start rendering your component, and start fetching the data and showing a placeholder. When the data loads you then render the content. All streaming SSR is, is doing the same thing on the server.
When you request your page it starts rendering and fetching that async data. It starts streaming the response back with all the placeholders in place immediately so the client doesnβt wait. However it keeps the response stream open. So when the data finishes loading it can be rendered and sent along the stream. We flush out script tags with it to inform the browser app itβs time to insert and hydrate the new parts as they come in.
So the final result is the synchronous part of the page sent immediately without waiting for async with placeholders very similar to the client loading experience. You see the content load in as available but this happens faster than client rendering since you were able to start the process with the initial server request instead of waiting for the page to be sent to the browser and the JavaScript be requested and processed. But best of all you get to write your apps the same way you always have. We donβt need to hoist out data fetching. It just has the same mental model as the client.
Itβs a best of most worlds scenario. And itβs clear to see why React is excited about introducing this in React 18.
Tyson: Wow, thanks for diving into that. I was going to ask about Suspense next. React had to really dig into things to make Suspense work, with a breaking change to a new render engine. Did you guys find it to be a tough thing to implement?
Carniato: Yeah, hahβ¦ I went through a couple different versions. I donβt think people realize how not apples-to-apples it is when a framework like Vue or Preact says it has Suspense. Rendering placeholders and new content off-screen is relatively easy. Itβs βtransitionsβ and concurrent rendering that make this more challenging. Those frameworks donβt support that.
My first model was really simplistic and was based around just branching realities at each nested control flow. But it required people opting in at each control flow, which was awkward.
My second (and current) approach meant collecting and forking each reactive atom when it was denoted by a transition. And then all future changes either are applied to the transition branch (if they are a transition themselves) or to both if they are regular updates.
This means that while I fork reality there is only one future that is being worked on concurrently. I couldnβt figure out how to crack multiple futures like React had mentioned and I figured it was good enough so I just released it a year ago and went with that. As it turned out, React just announced they too have gone to single future for React 18 so it seems I wasnβt alone finding multiple future realities really difficult to reconcile efficiently.
Luckily Solid was still 0.x and there wasnβt that much in the way of breaking changes. Remember Solid components render once and most communication is through the reactive system granularly. Unless the developer is hoisting out non-reactive values they read from multiple branches the rest is just taken care of by the fact that Solid controls the communication.
Tyson: Gotcha. I feel like weβve gone from coding to sci-fi with reality forking. I have just a follow-up question to that answer and one last questionβthanks so much for your time.
In React, Suspense depends on a data store that implements the API for interacting with the Suspense component. Does Solid do a similar thing via the createResource function? (I havenβt gotten into Solidβs getResource just yet.)
Carniato: Yes. The difference is being reactive and not re-rendering. It isnβt so much a cache but a special signal that knows when its data is stale, so that at some later point when it is read we can be sure to inform any Suspense context above it that it needs to do something. (Yes, Solidβs Suspense is implemented with Context API.) But the special primitive is a blessing since it also is used more like a cache on the server as it facilitates auto-serializing of data between client and server for SSR. And it helps us register promises so the server rendering knows when it is done and can close the stream.
Tyson: Awesome. So, you have put an intense amount of work (~800 commits since 2018 on the GitHub project) into Solid. What keeps you motivated?
Ryan Carniato: Itβs a funny thing. I donβt know when this changed. Like I decided one day that I wasnβt doing enough dev work at work as I got into more managerial positions and I was up late with my newborn daughter at the time. And I started tinkering and picking it up more at that point. Still just as a novelty for myself on a private repo. Then I found the JS Framework benchmarks and realized the approach [I was taking] could be fast so I open sourced [my project] so I could be included.
For a while that is what motivated meβjust shaving time and figuring out how to be the fastest. And then React announced Hooks and everything changed. Honestly I couldnβt believe it. It was like theyβd just pointed things Solidβs way. I started writing articles and for the longest time just knowing that we were on to something kept propelling me forward. I still feel that way with the things we havenβt released yet.
Every new problem seems to morph from a weakness to an advantage. Itβs an incredible feeling when things seem to work that well. I just canβt picture having things go any other way. Iβm hooked (pardon the pun).
Tyson: Yeah, itβs pretty extraordinary how the world seems to keep shifting to vindicate different design choices Solid has made. I know I learned some things. Is there anything youβd like to add?
Carniato: Probably only this. There is a lot of chatter and narratives around this framework vs. that framework and itβs all exaggerated. There are implications to the choices that frameworks and libraries make to be sure. But you always reach the end where you are building a product with the tools you have and the solution is a little messy and perhaps not the most elegant. But there is a reality and you have a deadline to meet.
As much as we like to do theoretical armchair racing and say things are βstrictlyβ better it only goes as far as the developerβs ability to use these tools.
If there is any takeaway from what weβve accomplished with Solid is that there is still room to explore and that established boundaries and conventions still have room to expand. Work with focus, but when you are on the other side evaluating things donβt be the person who just puts everything in a box and moves on. Solid wouldnβt be here with that mindset, nor will the next big thing.
Tyson: I think that sense of space to explore is very exciting.
Oh, one more question. Iβm curious, did Solidβs <a href="https://www.solidjs.com/docs/1.0.0#introducing-primitives" rel="nofollow">createSignal</a> syntax come first, or Reactβs <a href="https://reactjs.org/docs/hooks-reference.html#usestate" rel="nofollow">useState</a>? (Editor: They both use destructuring assignment.)
Carniato: Reactβs useState came first. I was staring at it for so long just hating every permutation. I was looking at:
const count = createSignal(0)
count.get();
count.set(value);
But I hated it. I knew I didnβt want to do the following either.
count()
count(value);
Nor:
count.value
count.value = value;
I thought of doing:
count()
count.set()
But it felt too clever. Still I needed a primitive so I was going to go with this.
But the plan was to try to push everyone to use what I call createStore now as the main primitive. It used to be called createState after Reactβs class state. So while I needed signals I figured everyone would just use:
const state = createState({});
state.count;
state.set({ count });
But then the second I saw Hooks I was likeβ¦ this is it! I have no idea why I didnβt think of returning an array. Iβd gone through so many permutations.
I saw it and it just worked. Over time people were more drawn to Signals than createState and it was confusing people coming from React expecting it to be the same as useState. So I renamed it to createStore.
Tyson: Itβs funny because when I first saw that syntax in React I was like, what the heck is this thing? But it really works well and itβs compact.Β
More by Matthew Tyson:


