Server Components are arguably the biggest change to React since its initial release but many of us in the community have struggled to get a handle on them. In this talk we'll try to break down the different moving parts so that you have a good understanding of what's going on under the hood, and explore the line between React and the frameworks that are built upon it.
Simplifying Server Components
AI Generated Video Summary
React server components simplify server-side rendering and provide a mental model of components as pure functions. Using React as a library for server components allows for building a basic RSC server and connecting it to an SSR server. RSC responses are serialized virtual DOM that offload code from the client and handle interactivity. The client manifest maps serialized placeholders to real components on the client, enabling dynamic rendering. Server components combine the best of classic web development and progressive enhancement, offering the advantage of moving logic from the client to the server.
1. Introduction to React Server Components
So, as you heard, my name is Mark Dalglish, and I'm here all the way from Melbourne, Australia, which means it took me 21 hours of flying to get here. So, thank you so much for having me. It's a pleasure to be here in London.
2. Understanding Server Components
When it was explained to us that JSX is just function calls, it became straightforward. However, the simple answer of server components being components that only run on the server is not sufficient. I want to simplify server components and provide a good mental model of how they work beyond the high-level APIs.
But when it was explained to us that it's really just a syntax sugar for writing code like this where you're just calling a function, passing in some arguments to describe what element you want, the element type, the props, the children, it was quite straightforward.
So now that brings us to the latest question in React, which is what are server components? Seems like a straightforward enough question. And the simple answer that people tend to reach for, I've found, is something along the lines of this where you say server components are components that only run on the server.
Now, the problem I have with this as an answer, beyond the fact that it's basically just rearranging the words of the question, is that it leaves me with no mental model for how they work, what are the constraints or tradeoffs, what does that mean for my architecture, what can I do, what can't I do with them, how do I use them at an API level from React? So I had very little to work with here. So the goal here for my talk is to help do to you what I had to work to achieve which is simplifying server components. Hopefully not oversimplifying them but simplifying them enough so you have a good mental model of how they work beyond the high-level APIs that you might write to generate server components.
3. Using React as a Library for Server Components
React can be seen as both a library and a framework, depending on how it is used. When working with server components, Next.js is often used, which introduces additional opinions. To understand server components directly in React, we will build a basic RSC server and connect it to an SSR server. The SSR server is responsible for client-side pre-rendering, while the RSC server handles rendering server components. We import the necessary libraries, such as Express and React server, to facilitate this process.
Now, the first thing that makes this tricky is the fact that React is torn between whether it's a library or a framework depending on whether you're using it directly or whether more likely you're using it via some sort of meta framework. So that means today, if you want to get your hands dirty working with server components, you're probably going to be using Next.js which means that you're not just learning about how to use server components, you're learning about a bunch of opinions on top that frameworks naturally have to provide. So you're arguably not much closer to knowing what it means to use server components directly in React.
Today, what I want to do is show you what it looks like to use it via React as a library because ultimately React, it is a standalone project and so what we're going to do is take an application like this, very, very simple for today's purposes, we're not even going to deal with nested components here, just a basic app component that renders an HTML tag, a body tag and a heading with some plain text inside. So what we're going to do today is start off by building a basic RSC server. Its job as you might guess is to render server components. Now I call that out because that's literally its only job, it's just to render server components and pass them off to the SSR server or to the client for live updates over the course of your running application. So like I said we're going to keep this very very simple, we're going to pull in express. Now I want to show you real code here and not diagrams, because this is what I felt like I needed in order to get some sense of what's happening here. We're pulling in express and here we're bringing in this render to pipeable stream function from react server dom web pack slash server. So we'll come back to that later. But we've got this function here and now like I said we're going to keep this very simple where literally any request to our express server we're going to render the same thing, but obviously you can imagine in a real world app you're going to be dealing with data coming into the server to describe what exactly you want to render. So here we're going to render to pipeable stream or a node stream our app component that we saw before and from it we're going to get an RSC stream and we're going to send that to our consumer. So we're creating a stream of a stream of the response of rendering this app component in our RSC server.
So now that we've got our absolutely most basic possible RSC server let's hook it up to an SSR server. Now I'm going to call out the elephant in the room straight away which is we've already got a server now we've got an SSR server. The word server is getting thrown around a lot. We've got server components and now we've still got this relatively old concept now in reactive server-side rendering and these two concepts are sitting side by side. To help demystify these two concepts what I've found is it helps in this world to reframe the SSR server as being about client-side pre-rendering its job is to just take that initial view of what the browser is going to render, render that to HTML on the server, and from that point on, the SSR server from a rendering perspective, its job is done. You're never going to talk to that server to get React components or HTML again so it's kind of like it's just pre-rendering. The server aspect still happens on the RSC server later. So here our SSR server is going to pull in some libraries, of course we're going to bring in the HTTP module, nodes HTTP module, we're going to pull in Express. Now one thing I want to call out here that's interesting is we're importing from React server on webpack slash client. Now that might seem surprising at first that we're importing from client, but think of it in the same way that your server might have a REST client on it or a GraphQL client. In the same way here we've got an RSC client and we're going to call this create from node stream function and then also we're going to return a stream from this as well. So again to keep it simple, any requests coming in we're going to render the same thing. We're going to talk to our RSC server and get the RSC response. So that app component we saw before is coming in from the RSC server. And this is where it gets interesting we call this create from node stream function. We've got the RSC response coming in and from that we're getting some virtual DOM and from that we can create HTML and send that to the client.
4. Understanding RSC Responses
We're turning RSC responses, which are serialized virtual DOM, into HTML and sending them to the client. This allows us to offload code from the client and get fresh markup without rerunning the code. Dealing with static HTML is simple, but the challenge is handling interactivity in client-side apps.
So here we've turned that RSC server into HTML and sent it to the client. So this is the line of code I really want to hone in on because to me this was the one that opened my eyes the most to what's actually happening is that we're getting an RSC response, we're getting something from the network and we're turning it into virtual DOM. Why that's interesting. Well virtual DOM is an easy concept for established React developers because we deal with this all the time anytime you have a component rendering JSX elements like this or assigning them to a variable in this case.
You're creating these elements in memory based on code that's executing live in that environment but the difference here I guess what's interesting is that server components is just serialized virtual DOM. It's taking React elements that were rendered on one server in one environment and sending it over the network to render somewhere else without needing the code that generated it. So this means of course that we can offload code from the client. This is the whole point that we can get fresh markup without having to rerun the code. So static HTML is simple to deal with.
5. Serialization and Client Manifest
If we nest the counter component within the tree of React elements, serialization becomes a problem. The counter function cannot be turned into a string and sent to the client. Interactivity is a challenge in server components. The JSX compiles into a React client reference, which is a placeholder for components that need to run on the client. The client manifest maps these references to instructions for resolving the code behind the component. The RSC server dynamically defines the code the client should download, enabling dynamic rendering of components based on user data. Server components allow the server to tell every consumer which code they need to download to render elements. With React, we can get new server markup dynamically while keeping client state. The client manifest maps serialized placeholders to real components on the client. Serialization is crucial, and the props must be serializable. Click handlers on client components cannot be serialized into the network tree of elements.
So if we were to take this counter component and nest it within this tree of React elements that we now have a problem if we're trying to serialize it. So if we were to take our basic implementation of stringifying the arguments to React create element we can't turn this counter function into a string there's no way to to represent that in that format so we can send it over to the client. They need access to the code behind this not just not just the component in memory.
So that means that it's not even just strictly about components really it's just about being able to turn these things into a string to send over the network to serialize them. So if you were to have a button component with a click handler you're going to run into the same problem. So how do we get interactivity? Let's make this even slightly more complicated here and say that we're going to render our counter component in the tree with some props. We're saying the initial value is zero. So what does the jsx compile to in an RSC world if we can't send the real component over the network?
So here we've got our app component and of course the jsx is going to get compiled into something like this. We've got our create element call to html. We've got our create element call to body as we've seen before. But here's where it gets interesting because we have this component in the mix what we're actually doing in terms of what the jsx turns into is a react client reference. So this is like a placeholder in the tree saying we don't have the actual elements here we have a reference to a component that needs to run on the client. It has an id here of counter and then of course being create element we've got the props inline there as well. So this id of counter here that needs to map to something so we know how to resolve that to the real component. And that's where the concept of this client manifest comes in. This is an object that maps these ids back to basically instructions for how to resolve the actual code behind this component. So here the id it's the module id in the module system so the client is going to need to import from slash components slash counter in this example. Name is the export name so if it was export const counter you see counter here. If it was export default function you would see default here and then chunks with some information on what code to load as well. The thing I want to highlight here that to me was like the real eye opener of how this works is that the the RSC server gets to dynamically define which code the client should download. That's interesting because if you think about the use case that probably this is coming from in a Facebook sense is like the news feed where you may have a feed of components that are rendered dynamically based on data per user and what components are going to be on the screen is completely dynamic at run time. Server components means that the server can tell every single consumer of which code they need to download in order to render the different elements in that feed something that would be really hard without RSC. So in some sense if you squint you can feel like you're going back in time a bit here in a good way in that you know you're going back to this model of having server-side templating that just renders plain markup and then the dynamic parts of your page are script tags that either have inline scripts or references to external scripts for how to bring that static markup to life. So it's kind of like going back to that model but we do it in a way that fits into the way we've come to expect React to work where we get a single component tree we get to think in that sort of relatively single paradigm even across server and client and we don't have to deal with the low level intricacies of script tags we can think at a component level. So from the client's perspective this means that we can get new server markup from the RSC server dynamically over the lifetime of the app while keeping client state. This is the whole reason that we use React versus just setting inner HTML all the time. So if we have our client manifest we have this object that says how do we map these placeholders in the tree that we've serialized to real live components on the client and again client being both the SSR server and the browser. What we need to do is pass that in as the second argument earlier I had an empty object just to keep it simple but here when we render our RSC stream we're passing in the client manifest so the consumers of our RSC server are able to get instructions for how to resolve these client components that are nested within the tree. So the important thing to realize here is that it's all about serialization, it's about taking a tree of React elements and serializing them and that means that the props need to be serializable as well. So in our basic example you can see that because our initial value here is the number of 0 that's completely fine to serialize but this is why you can't do things like again put a click handler on your client component, because if you think about it at the end of the day it needs to be serialized into this tree of elements to go over the network and we can't do that.
6. Understanding React's Server and Client Components
Server components are just virtual DOM over the network. RSC is all about having serializable components and trees of React elements that we can send over the network. It combines the best of classic web development and progressive enhancement with the benefits of modern development. The complexity of infrastructure, tooling, and developer considerations should not be downplayed, but a simple mental model can help understand how server components work and their trade-offs in real applications.
So in our basic example you can see that because our initial value here is the number of 0 that's completely fine to serialize but this is why you can't do things like again put a click handler on your client component, because if you think about it at the end of the day it needs to be serialized into this tree of elements to go over the network and we can't do that. So if you want to have click handlers they need to be in the source code of the component but not in the props that are in that element tree.
Interestingly of course in the React world children are basically just props. At the end of the day they're serialized in a similar way and this is what allows us to do this interesting technique of nesting server components within client components. Because if you think about again that React tree, we've got our placeholder here, this React client reference for an alert component with a title prop, that's all serializable, and then the children here is a create element call, so it's a plain html element. Now this obviously in real life in a real project could get a lot more complicated and could have nested server components, but the key point here is that we have a client component whose children is a result of another server component. And to me I think seeing it as a serialization problem helps me understand the mechanism for how I can nest components in the React tree, but I can't do it in the source code.
So there are two types of elements we're dealing with here, the first is plain old serializable html elements, again, it's like just static markup that we're talking about, and then for anything more complicated than that or interactive we've got instructions in the React tree for resolving and rendering components, and this is where the whole concept of client components comes in. Now, of course, you don't write this by hand, just like JSX that we showed before, this is extending JSX to be able to do a lot more, in fact, and this is where bundlers come in, the whole point of using bundlers is to deal with this kind of complexity for us, and so when you as a developer put a use client directive at the top of a client component file, and then elsewhere in your app, in a server context, you're importing this counter component, you don't get a reference to the actual function, because, again, we can't put that in the React tree, counter in that context actually automatically becomes this object that we saw before, this client reference with the ID of counter put in there for us, and of course, this ID needs to map to something in the manifest, and so that's going to be managed for us as well, this generation of this object, of how do we map these placeholders in the React tree to the components that are going to run on the client, this is all managed for us. Because you need this integration with the bundler, this is why these long-named packages exist, you have the React server, Webpack, VEET, these exist to manage the differences between the different bundlers for us. And of course in practice, just like SSR was years ago when we got started with SSR, wiring this up into a real application is complicated, and this is where frameworks come in, of course, that's why, again, today you're going to reach for a framework if you want to use server components today. But if someone asks you, what are server components, hopefully as a result of this talk you're not just going to say, again, rearranging the words that server components are components that only run on the server. You can say something like server components are just virtual DOM over the network. You can think about the fact that ultimately RSC is all about having serializable components, having trees of React elements that we can send over the network and with that obviously comes some restrictions, but when you understand the mechanism it makes more sense. What I like, as someone who has been doing web development for a long time at this point, is that I get to think about it more primitively as server side templateing with script tags, but done in a way that feels more at home in React. Again, getting us closer to that world of the best of classic web development and progressive enhancement, but with the benefits of modern development that I have come to expect with React. I don't want to downplay the complexity, the very real complexity that is at play here, both in terms of the infrastructure, the tooling, and what you have to think about as a developer, but hopefully, what I've done is given you a simple mental model to work with so that when you go back to your work and you look at server components, maybe for the first time, or maybe you've been working with it for a while, you have a simple mental model for how it works in practice and what those trade-offs mean for you in terms of your application.
Understanding Server-Side Components
That's it for me. Thank you so much for listening. I really enjoy this because I use Next.js. Now, it's my go-to whenever I'm building an application and I understand how I use server-side components, but I've never necessarily dug deeper into how that information is going from the server to the client. One thing I wanted to ask is when you see server side components and the way they're being used, what kind of mental models do you think people can use to make the decision about whether something should be a server side component or whether they should be doing it in the client? Now, the questions are definitely pouring in. We've got one which is asking, for server-side components, are there any React tools that you would recommend for working with debugging them? We also have another question. This one is, well, this kind of bounces back to why people use server, why people should use server components. What are some of the big perks to server-side components in your mind?
That's it for me. Thank you so much for listening. I really enjoy this because I use Next.js. Now, it's my go-to whenever I'm building an application and I understand how I use server-side components, but I've never necessarily dug deeper into how that information is going from the server to the client. I'm going to re-watch this talk for sure and compare notes with it as well.
For those of you who are joining, if you do have any questions as well, make sure you head over to Slido. I think maybe the Slido QR code wasn't working for some people on specific devices, so I just wanted to let you know that you can go to slido.com and use the code 2010, so 20 and then 10, to ask your questions. And then as the questions come in, I will ask them to mark. I really also loved the children are props, just props point as well.
One thing I wanted to ask is when you see server side components and the way they're being used, what kind of mental models do you think people can use to make the decision about whether something should be a server side component or whether they should be doing it in the client? Well so I guess first of all, like we were saying in the talk, to some degree it's forced on you as soon as there are certain things you're trying to do. So again, if you're trying to have interactive components that again can't be serialized, you just have no choice. But one thing I guess that's interesting that, I mean I can't speak to you from my own experience, but I know that one of the trade offs even when moving to server components is that even though you're moving code off the client, you still have to serialize all of those elements and send them down as data in that payload. So sometimes there's a trade off where you end up actually serializing a lot of content as HTML that you could have just sent as data as we do today, well before server components in React. So I think you might even find that there are cases where something strictly could be only on the server, but you decide to move it to the client because you realize those RSC payloads are getting too big. So there's, I think that beyond the fact of whether or not something has to be a client component, you may even find there are times where you might want to move the slider in terms of how much happens on the server versus the client. No, that makes sense. That makes sense.
Now, the questions are definitely pouring in. We've got one which is asking, for server-side components, we are very comfortable. There are so many tools for debugging client-side components, but for server-side components, are there any React tools that you would recommend for working with debugging them? Yeah, it's a good question. So, I mean, I haven't dug into it so much in terms of running real applications. This was very much, for me, a theoretical exercise of looking at it from a framework perspective of how do you even implement something? But what I would say is, again, to talk about the complexity, my perspective on this is that I think this is why, even though people are excited about it, there is also some sense of apprehension around adopting it. Because it's a big change to the way your app runs in production and so that brings a lot of questions around, like you said, around debugging or looking at things like performance. I'm sure there are people actively working in this space, so sorry if I haven't brought up your work, but, yeah, it's a good question, that it definitely changes the model in a big way. If anyone has any debugging tools recommendations, drop it into the Discord.
We also have another question. This one is, well, this kind of bounces back to why people use server, why people should use server components. People ask about SEO or faster time to load, and the fact that there's a cost associated with running the server and whether it's worth it. What are some of the big perks to server-side components in your mind? To me, I think the big benefit is just about doing less work on the client, and it's one of those things where whether that's important to you really depends on the type of product you're building. For example, in my earliest work with React doing server-side rendering, like I said, the whole reason we went big on React early was because of its server-side rendering.
Benefits of Server Components
Despite the downsides of React's performance, it still provides real HTML and quick rendering. However, for static pages with minimal interactivity, the amount of code and hydration work can be excessive. Server components offer the advantage of moving logic from the client to the server, providing more power to tailor the product.
And so despite the downsides of React from a performance perspective, it was still a huge win for us, because when Google hits our site or users hit our site, they would get real HTML and they would get something on the screen pretty quickly. But there are certain classes of application where the amount of code being downloaded on the client and the hydration work needed is too much considering perhaps how static a lot of the page might be. And so I think maybe it really depends on how much of a website-versus-app you are is definitely one consideration. If you feel like, at the extreme end, you're saying, I have a blog, for example, to me is an extreme case where why do I need to rehydrate a wall of static text? It's pointless. Maybe there's some interactive pieces at the bottom or in the middle of the document and I only need code for those. But if I want to stay in that React environment, because I do like building even simple things like blogs, I do enjoy building them in React. To me, the benefit of something like server components is that we have more power at our disposal to move logic out of the client into the server where it makes sense for your product.