React: how far can components go?
It seems that not a single day passes without someone on Twitter wondering what RSC is really about. I have made my own attempt at cracking it (“RSC is React Server + Component”), arguing that RSC is best understood in the context of the new React architecture, not in isolation. What we have here is not some new magic kind of Components, but a server-client architecture that speaks React on both ends and grants Components, if placed on the “server” side, direct access to new computing environments and their resources.
After another month of closely following the RSC discourse, I still stand by it. And personally, I think React might be making mistakes by picking RSC as an umbrella term for everything this new architecture brings. Nonetheless, I appreciate the new React architecture as a unified model for UI development that brings both ends under a single, coherent paradigm. A true “one app” architecture. This is a significant departure from most JS-centric web frameworks, including those built on top of React, that provide various server side features (e.g. Next’s
getServerSideProps and API routes, Remix’s
action, etc.) that are doubtless useful but fundamentally external to the core UI application placed squarely on the client side.
In practice, the new React architecture isn’t without pitfalls. Without careful planning, React Components with access to server resources (i.e. RSCs) still suffer from the same “waterfall” issues even though they’re now hidden from the network tab on the browser devtools. Unlocking this new architecture also comes at the cost of increased bundle size and RSC support in tooling is still at an early stage.
That said, I believe they are more growing pains than critical difficiencies inherent to the new architecture. If React can successfully address those pain points via improved tooling and messaging, 2024 shall be the year when everything finally clicks for the mainstream audience. As I noted in an earlier blog post “React vs the world”, React has a proud history of challenging the contemporary norms and conventions of UI development, including the ones it helped to popularize, and winning over the world. Despite all the noise and confusion in the ongoing RSC discourse, React appears confident that it can repeat the history again. “Relax, it’s React.”
Solid: how far can primitives go?
If anyone asked me, I’d say Solid won the JS frontend in 2023. Not because it overtook React in usage (not even close!), but because its idea won. Pretty much every framework but React implemented Signals in some form, and even React adopted the language of reactivity to describe its optimizing compiler under development (“A better way to understand React Forget is as an automatic reactivity compiler”). But this begs a question: what’s next for Solid? Will the success of fine-grained reactivity lead to the fall of Solid, which can no longer stand out from the crowd?
Solid’s answer is no. In 2024, Solid will reach 2.0 with a renewed reactivity implementation and Solid Start, the official Solid web framework, will reach 1.0. As I follow the ongoing development, it’s getting clearer to me that I, like many others, have been missing the point of Solid. The point of Solid is not Signals or fine-grained reactivity. That’s more a happy accident. The real point is about primitives over abstractions, explicit over implicit, and simplicity over ease of use. And that’s how Solid remains a stand-out in the crowded JS framework space where others often seek to attract developers with easy-to-use abstractions that “feel magical.”
And Solid, too, is eyeing on solving the server side of the equation with a new set of primitives albeit from a different angle. Instead of creating its own version of “server component,” Solid is betting on streamlined support for server function. Solid’s server function looks similar to React’s Server Action; they’re both marked with the
"use server" directive and with bundler’s help, gets turned into RPC calls.1 When combined with Solid’s official router and its Data API (
createAsync, and more), Solid’s server function can offer pretty much everything you want from data fetching/server state management solutions: direct access to server resources via flexible RPC interface with caching and route-level parallel fetching. The best of Relay, Tanstack Query, tRPC and Remix all in a single package!
Also, recall that a Solid component is more like a “setup” function that runs once to wire up the reactivity graph than a “render” function that continually re-runs to produce the latest UI. This means that the potential gain from moving components to server is much smaller for Solid than for React. Plus, Solid already has a minimal bundle size and its hydration cost is relatively cheap. So it’s understandable why Solid doesn’t consider “server component” to be a critical piece of its take on the “one app” architecture.23
Notably, Solid’s approach does not require any radical shift in mental model. It still feels very much client centric, but with additional primitives that can be combined to bring server closer. It’s modular, incremental, familiar, and practical—potentially a winning combination.
htmx: How far can hypermedia go?
htmx presents an approach that is fundamentally at odds with what has been the mainstream for over a decade, i.e. a Single Page Application entirely controlled by a React-like JS framework, directly manipulating DOM and exchanging data with (mostly) JSON APIs. Instead, htmx extends HTML as hypermedia. With htmx, we can encode sophisticated behaviors directly in HTML as attributes—this is an exact opposite of JSX, “an XML-like syntax extension” to JS for templating purposes. An htmx app is server centric like the Web 1.0 days, but is able to match most SPA apps in its ability to support advanced interactivity and modern UX—all with little to no client side scripting.4
What’s particularly intriguing to me is that htmx shows us a different route to the “one app” architecture by the way of killing the client-side.5 It’s important to note that htmx is not a 100% solution—it shouldn’t be controversial to state that the interactivity ceiling is much lower for htmx than, say, React or Solid. However, it can get you 80% there with radically less complexity. No extra dependencies, no build step, no advanced tooling (now re-written in Rust!), no complicated state management, no “double data” problem, no hydration mismatch… Just write your HTTP server and return HTML!
In a way, htmx is not a solution, but a question: what are you really building—does it really need more than htmx?
Which way to cross?
Though most (all?) web frameworks aim to bridge the “network chasm,” I’m learning that this chasm cannot be closed, it can only be crossed. So which way to cross it? React, Solid, and htmx all present distinct answers here. With React, we see an attempt to cover the full spectrum at once within the same Component model. With Solid, we see a focus on the client side with new primitives to bring server close to it. With htmx, we see a good old server centric approach but with a twist that is supercharged hypermedia.
It may be too early to predict which way will win out. Or maybe such a prediction is a mere nonsense—there is no silver bullet, remember? Different approaches with different trade-offs will benefit different use cases differently. Nonetheless, I believe that React, Solid, and htmx all point to what might be the next step in the evolution of web development and UI engineering. If and when they succeed, perhaps crossing the network gap will finally feel as though the gap’s closed.
An earlier iteration of Solid’s server function API was
server$(). Solid later made a pragmatic choice to stay aligned with React and adopted
"use server"—just like it did with JSX early on. You can find the rationale for this decision in this release note. ↩
In fact, Solid did run an experiment on “server component” with its “Islands router” in 2023. Examples of this experiment can be found under
/archived_examplesdirectory of the https://github.com/solidjs/solid-start repo, including this “Movies” app. The Islands router approach was later (temporarily?) dropped as Solid switched its focus to server function with new router Data API. ↩
To be fair, Solid’s approach cannot fully match React’s new architecture and falls short in solving the “double data” problem where the same data appears twice, once in pre-rendered HTML during SSR and once again in
<script>as a JSON blob to be used during hydration. ↩
A full explanation on how htmx achieves this is clearly out of the scope of the current blog post. But if you, dear reader, are interested in learning more, I strongly, strongly recommend Hypermedia Systems, a book-length treatment of the topic co-written by the author of htmx. If you’re short on time, the official htmx.org website also has many good essays. ↩
This is not to say that htmx fully eliminates any need for client-side scripting. However, htmx demands that such scripting must be in the service of hypermedia, rather than replacing or competing against it. ↩