RSC is more for the giants, not us
When Remix put React Server Components (RSC) on their road map, I was a bit thrown off. The main reason I've been so drawn to Remix over the years is the beautiful simplicity of their loader pattern, which is so powerful that it didn't seem they would ever need something like RSC. And yet, they're adding RSC to their loader pattern.
This forced me to step back and attempt to answer the $150M question: What does RSC give you, again?
After a few weeks trying to answer this, I've become convinced that everyone has gaps around the reasons surrounding everything RSC-related. There's not as much cost-benefit analysis going on as I'd like. There are some good technical dives out there, but not a lot that get at the tradeoffs.
Since RSC are likely to become the new standard, it seems worth it to attempt to have a mental model around these tradeoffs, even if I'm not convinced I know everything there is to know about RSCs.
But I am convinced that know one does. So let's dive in.
Do you need streaming SSR?
After a React-influenced decade has left us all with bloated SPAs, I was eager to see how RSC would influence server-side rendering (SSR). SSR has its own set of challenges - hydration & interactivity being the biggest ones - but how will RSC move the needle on these challenges?
The most notable benefit I see from RSC is the concept of streaming SSR.
And yes, I'll be obnoxiously referring to that as SSSR from here on out, and you can't stop me. 😤 I purposefully add that streaming adjective to point out that this is something different from the SSR we're used to, which isn't obvious if you're like me and have been equating the server in RSC to server-side rendering.
If client-side rendering (CSR) is the strategy of "send down JavaScript that the browser will run to generate the HTML" and SSR is "the server will run code to generate all the HTML and send that down", SSSR is a piecemeal approach. You don't have to wait to generate every piece of HTML the client will need, but just the bits they need right away and send the rest down later.
This gives quick wins by allowing you to get something interactive to the client before your slow-running logic finishes. (A recommendations widget comes to mind as an example.)
On the surface, this sounds like a big win. But what is the cost? RSCs are a different paradigm than your React devs are likely used to. For me, this change in mental model isn't trivial, and thus I need this streaming improvement to be worth it.
And I have my doubts that it's worth it for the average product team. If you're a big company with a big application with big scaling challenges, then this is a big deal and likely worth the extra complexity.
If you're a startup just trying to prove product-market fit, you likely won't see much gain, but you certainly will feel the pain of a whole new component paradigm. (And if you're a hobbyist, then you likely want to stay far away from that complexity and just get your app done.)
If I was using a framework that had this as easy opt-in - i.e. just drop in a <Suspense>
somewhere - then it's likely not a big deal. I'll take on that complexity when I decide I need it. But the only major implementation we've seen is Next.js which requires you to use a whole different router which resulted in there being two largely separate sections of their docs.
It's important to call out that you can still do the classic SSR with RSCs, but why? You've added a bunch of complexity for no gain.
I can't help but be suspicious about whether or not this is worth it for most teams who were likely doing just fine with Next.js' getServerSideProps
and/or Remix's loader pattern.
As a thought experiment, think of a major company that would definitely benefit from this optimization. Maybe one where their core business model is to get you hooked on their web app and never let you leave, and thus they really want to show you something on the page even if they're still doing background work? Hold on to your answer. We'll come back to it later.
Do you want coupled components?
Sam Selikoff's 2023 Next.js Conf presentation gives a deep dive on what I'm calling coupled components.
(He uses the phrase composability but I think that's imprecise; all components are in theory composable and reusable.)
The key takeaway is that RSC allows you to strongly couple any back-end logic with the visual component that will leverage it. I can totally see many devs loving this functionality. And I can equally see just as many devs furrowing their eyebrows at the thought.
Coupling correctly is hard and everyone has opinions on the concept. My stance is that coupling is great when you chose the right boundary lines and kills your productivity when you get it wrong.
That's a bit of a tautology - it's good when it's good and bad when it's bad - but it drives home the point that coupling is something that's unique to your application and use case.
I can come up with scenarios where I would want this back-end, front-end coupling. I once was in a company where the application's nav bar needed to be the same - both in UI and in authorization/authentication logic - across all subapps. And every sub-application was owned by a different team than the one owning the nav bar. This is likely a great use case for a coupled component. Nav team makes one component and gives it to everyone else. (Granted sharing between teams has its own hurdles, but RSC would handle the coupling which is still a win.)
I can come up with scenarios where I would not want this back-end, front-end coupling. If you change the above requirements and every app has a slightly different UI goals, then coupling will make each team have to hack at the shared comp until it looks like they need it to.
Whether this is worth the complexity is dependent on your team and its use cases. I even suspect it's dependent on how pro or anti your team's philosophy is against the dreaded prop drilling approach. An alternative for coupling the back-end logic of "how do I fetch the data I need to display" to the UI is to have a sensible top-level component do the fetching and pass it down via props.
I'm personally fine with prop drilling since anytime I've seen it actually go bad in the wild is when the component hierarchy itself was poorly organized. We rethink the hierarchy and usually the amount of props became fine. But I rarely argue with folks over prop drilling; you either hate it or treat it like a symptom of other issues. And I've never seen anybody change camps.
So again, we have to determine if the tradeoff here is worth it for our team.
Let's continue our thought experiment from earlier. Think of a major company that would definitely benefit from this type of coupling. Maybe one where they are incentivized to track every little thing you do and thus every piece of UI interaction likely has some back-end tracking logic associated with it? Or maybe one where the app is so gigantic the component hierarchy is likely going to be unbearably deep no matter what? Hold on to your answer.
How much are you willing to pay for performance?
RSCs are, in theory, going to be more performant when you're fully leveraging them. Streaming means you can SSR only what you need (i.e. island architecture) and your bundle size will be smaller since you're only sending down enough for what needs to be interactive. Running your back-end logic directly through server actions (as opposed to going through an API endpoint) will make things smoother.
But I always get suspicious when the word "performance" is thrown around. It's a reflex at this point. I've seen that word thrown around as a conversation ender because no one expects anybody to question something that improves performance.
I always want to know how much it will cost me. I question how much of an improvement you'll get for most folks using a standard SSR framework. Even Remix held off on RSCs for a few years since it was already beating RSC performance.
But if you're on a SPA, then I bet the performance gains are noticeable and likely worth the extra complexity. My initial reaction to this point was that most of the React world is likely still in a SPA, and thus this feature is important.
But RSCs are actually being pushed through the frameworks, most of which already had SSR. The notable exception would be RedwoodJS, hence why it's always made a lot of sense for them to approach RSC since it will help them get to SSR. I'm looking forward to see how its implementation holds up since it could very well make it worth the potential foot guns RSCs offer.
(Full disclosure: I've been working with the RedwoodJS core team on a form project, but not RSC directly.)
But let's go back to our thought experiment. Think of a major company that would definitely benefit from any performance improvement, no matter how small. Maybe one where the app is huge and contains several back-end calls to send data to advertisers but they can't risk letting things slow down because you might leave the app, thus killing their business model?
SSR for the heavyweights
Revisit the thought experiments. A certain archetype of a company begins to emerge as a key beneficiary. A big tech company product that needs you to live in the app.
It isn't a bad thing that these tradeoffs make sense for this type of company. It's just important to call out so we can all weigh the tradeoffs accordingly.
Frankly, I'm a little disappointed in myself for not connecting the dots sooner. I think I was so caught up in getting SSR in more places that I ironically missed the trees for the forest.
We've gone through this before. React was created by Facebook and has always been a tool more tuned to build a Facebook-like app. The SPA strategy makes sense when you've got a stream of likes and pokes that you want to get to the user ASAP so they never leave your app and you can gather as much data on them as possible so you can turn around and sell that data, through a very hazy definition of consent, to a bunch of third-party advertisers.
But most of us aren't building Facebook and thus I'm not sure SPAs (and maybe even React itself depending on how grumpy I'm feeling when you ask) ever truly made sense for the rest of us. The industry kind of went into a "if it works for Facebook it should work for us" mindset. It's our industry's version of the bandwagon fallacy.
After spending the last few weeks going deeper into RSC, I'm convinced we're following the same mistake.
If you have a large organization with several teams that would benefit from sharing coupled UI and back-end code, then coupled components are worth the complexity of RSCs. If you have a large application where every second of performance is existential to your business, then SSSR becomes a significant improvement over SSR as opposed to a nice-to-have.
But the majority of development teams probably don't need this. For me personally, I just want widespread and common SSR patterns. You could argue this already exists since this isn't really an issue outside of the React ecosystem (Nuxt, Nest, Astro, etc).
But I was thinking a definitive pattern from the industry giant would be a net positive for the rest of the industry.
This isn't the end of the world. DALL-E originally gave me a photo for this article that was way too reminiscent of the original Godzilla. This isn't that serious.
There just seem to be a missed opportunity here. Mayank already detailed the long list of features that could've been focused on in place of RSC. It's a little unsatisfying knowing there were points left out on the field. I don't hate React. I've just been disappointed in it for a while now and thus I've been bearing it.
I suppose that's not changing anytime soon.