Introduction
Before delving into the concept of React Server Components, let's take a brief journey through the history of how we arrived at this point.
A few years back, React web applications used a method known as Client-side rendering. In this approach, when a user loaded the application, an empty <div>
element with the id "root" was initially sent to their browser. The user had to wait for all the JavaScript to load before the app could construct its structure and content.
In an effort to address this issue, Server-Side Rendering (SSR) was introduced. With SSR, static HTML files were sent along with script tags and then hydrated. After the JavaScript bundle was downloaded, client-side React took over, enabling interactivity. The process involved the following steps:
A user requested a website.
The Node.js server received the request and generated static HTML, which was then rendered using the React application.
The HTML was sent to the client.
The challenge, however, was that although the client received the basic HTML structure of the page and possibly some styling, the dynamic data was not initially visible to the user because it was not included in the initial content sent to the client. As a result, after the initial page is delivered to the client, a secondary request is typically sent to the server to retrieve the data required for filling in the page's content. This often occurs because the function responsible for requesting this data is located within the initially loaded JavaScript code. In simpler terms, until this JavaScript code reaches the client's side, it cannot initiate a request for the dynamic data needed to complete the page.
Frameworks like Next.js created their own way of solving this problem by providing a way for code to run exclusively on the server. This was implemented using getServerSideProps. It fetches data needed for that component and passes it as props. Therefore the component doesn't need to make a request for data after being rendered on the client. This was a great solution but had a few challenges. The chiefest of them all was the fact that this could only be applied to page components. It couldn't be done on other components. React Server Components solves this problem and many more.
Introduction to React Server Components
Server components are a unique type of component in React that execute exclusively on the server. They enable direct database queries within components. These components differ from traditional ones in several key ways. They don't handle state, don't trigger re-renders, and don't require the use of useEffect. Instead, they execute only once, and they do so on the server.
But wait, isn't that a tad confusing? I know what you mean! When I first heard about Server Components, my brain did a somersault. But let me clear things up for you.
This confusion arises from a common misunderstanding of React components. It's important to remember that React components are initially compiled into JavaScript before being sent to the client. Browsers are not equipped to interpret React syntax, JSX, or similar constructs. Instead, they work with the compiled JavaScript code. When dealing with most components, the JavaScript is transmitted to the client, where the page's interactivity is processed, and the outcome is displayed. Now, here's where Server Components work their magic. Instead of sending this translated JavaScript to the browser, they're a bit sneaky. They keep it on the server. They say, "Hey, browser, we got this!" Then, they work their mojo, create the final result, and send that over to the browser. It's this result that the browser actually renders for your users. Server components follow a different path. The compiled JavaScript doesn't make it to the JS bundle that is sent to the browser. Instead, the server generates the result of that JavaScript, and this result is what gets sent to the browser for rendering. Server components render in a custom format that has no standard protocol but is akin to a JSON format. The react DOM recognizes this format and renders it appropriately once it is recognized.
In simpler terms, Server Components run on the server, and the server sends the ready-made result to the browser, while traditional components rely on the browser to interpret and execute the JavaScript code they contain.
React Server components is not a feature in React 18. No! It is the name of the new paradigm. For you to really grasp you must understand the two types of components that make up this paradigm
Client Components: They are the traditional components we've always worked with. They can run both on the server and on the client.
Server Components: These are the new kind of components that were introduced. They run exclusively on the server.
Where can this new paradigm run
Unlike previous updates in React that could be compatible with every environment, this is quite different. At the moment this article was written, it only works with Next.JS 13.4+ using the newly introduced "App Router"'.
What's different about the syntax?
With React Server Components, think of every component as being Server Components by default. In other words, they live on the server, doing their thing. But here's the cool part – if you want a component to be a "Client Component" meaning it runs on the browser, you have to be explicit about it.
To do that, all you need to do is add a simple note at the top of your component file. Just type "use-client" as a string, and bam! You've told React that this component is meant to be a Client Component. This little tag is like a secret handshake that gets your component into the JS Bundle that's sent to your client. So, it's in, and it's ready to work its client-side magic. Easy, right?
When should I use server components?
About that yeah, if your component uses states and effects, it should be a Client Component, otherwise, leave it as a Server Component.
Now the tricky part
You may ask: "But you said Server Components do not re-render. What if it is a child component of a Client Component that manages state and effects?"
Actually, that's quite simple. Everything is about the mental model you have of the paradigm.
It is impossible for Server Components to re-render because they don't make it to the client. "So how does react handle Server Components being children of Client Components?"
I want you to think of a tree with branches. From the root, it is by default a Server Component. Now the branches of this tree are formed by imports. I mean when A parent component imports a component to be rendered in it, there is a branch between the importer and the imported. (There is a reason I am not using the term "Parent and Child/Children"). So when React encounters a component being imported by another Client Component, it effectively marks that imported component, along with all the components it imports, and any subsequent components in the import chain, as Client Components as well.
Now that seems like an issue there, cause how do we handle things like global state, eg: Authenticated state, Light/Dark mode, etc?
That's the reason I refused to use the idea of "Parent and Children"
A workaround for this is by abstracting the global state into a context which would sit in the Client Component then wrap your app in the provider you create from that context. The reason this will work is because the Client Component is not importing the application. It only wraps it. When the global state managed by a context provider changes, only the components that use that state will re-render. Other components that don't depend on that specific state won't be affected and won't re-render unnecessarily. This is one of the advantages of using context in React for managing global state.
In a later article, I will deal with this workaround properly with sufficient code examples.
Conclusion
In summary, React Server components is a new paradigm introduced in React 18 and for now, can only be run in Next.js 13.4+. It consists of two kinds of components: Server Components and Client Components (Which is specified by adding "use-client" to the top of the file). Server components run exclusively on the server and their compiled JavaScript does not make it to the JS Bundle sent to the client, rather, it's the result of their computation that is presented to the client. They also do not manage state. Client components can run both on the client and on the server. They manage state and their compiled JavaScript makes it to the JS Bundle that is sent to the client.