React Server Components (RSC) is a promising feature that can have a significant impact on page load performance, bundle size, and the way we write React applications. This feature is still experimental in React 18, but it's worth understanding how it works under the hood.
Separation of server and client components
React Server Components allows the server and client (browser) to work together to render your React application. Some components in the React element tree are rendered by the server, and some are rendered by the browser. This is not the same as server-side rendering (SSR). SSR simulates an environment for rendering a React tree into raw HTML, but it does not distinguish between server and client components.
The React team has defined server and client components based on the extension of the file in which the component is written: if the file ends in .server.jsx, it contains server components; if it ends in .client.jsx, it contains client components. If it ends in neither, it contains components that can be used as both server and client components.
Life of an RSC Render
The life of a page using RSC always begins on the server, in response to an API call to render a React component. The server then serializes the root component element into JSON. The end goal here is to render the initial root server component into a tree of basic HTML tags and client component placeholders.
The server then sends this serialized tree to the browser, and the browser can do the work of deserializing it, filling the client placeholders with the actual client components, and rendering the end result.
RSC and Suspense
Suspense plays an important role in RSC. Suspense allows you to throw promises from your React components when they need something that is not yet ready (fetching data, lazily importing components, etc). These promises are captured at the suspense boundary. When a promise is thrown from rendering a Suspense subtree, React stops rendering that subtree until the promise is resolved, and then tries again.
RSC Wire Format
The server outputs a simple format with a JSON blob on each line, tagged with an ID. This format is very streamable - once the client has read a full line, it can parse a snippet of JSON and make some progress.
Consuming the RSC Format
The react-server-dom-webpack
package contains the entrypoints that take the RSC response and rebuild the React element tree. When the server finishes loading the data, it outputs the rows for the module reference - which defines the module reference to the component - and the React element tree that should be swapped into where the reference is.
RSC vs. Fetching Data from Client Components
Whether RSC is better than fetching data from client components depends on what you are rendering to the screen. With RSC, you get denormalized, "processed" data that maps directly to what you are showing to the user. If the rendering requires multiple data fetches that depend on each other in a waterfall, then it is better to do the fetching on the server - where data latency is much lower - than from the browser.
RSC and SSR
With React 18, it is possible to combine both SSR and RSC, so you can generate HTML on the server and then hydrate that HTML with RSC in the browser.
Server-Side Rendering (SSR)
Server-side rendering (SSR) is a technique where a React application is rendered on the server into a static HTML string, which is then sent to the client. This can improve performance and SEO by allowing the page to be displayed before all the JavaScript has been loaded and parsed.
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App';
const server = express();
server.get('/', (req, res) => {
const appString = ReactDOMServer.renderToString(<App />);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="root">${appString}</div>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
server.listen(8080);
In this example, the App component is rendered to a string on the server, and that string is inserted into the HTML response. The client then "hydrates" this static HTML into a fully interactive React application.
React Server Components (RSC)
React Server Components (RSC) allow the server and client to work together to render a React application. Some components render on the server, and others render on the client. This can improve performance by reducing the amount of JavaScript sent to the client and allowing the server to fetch and render data directly.
Here's a simple example of RSC:
// Message.server.js
import {db} from './db.server';
function Message({id}) {
const message = db.messages.get(id);
return (
<div>
<h1>{message.title}</h1>
<p>{message.body}</p>
</div>
);
}
export default Message;
// App.client.js
import {useState} from 'react';
import Message from './Message.server';
function App() {
const [selectedId, setSelectedId] = useState(1);
return (
<div>
<button onClick={() => setSelectedId(selectedId + 1)}>
Next
</button>
<Message id={selectedId} />
</div>
);
}
export default App;
In this example, the Message server component retrieves and renders a message based on an id prop. The App client component maintains a piece of state (selectedId) and renders the Message server component with the current id.
Practical Recommendations
Understand the difference between server and client components: Understanding which components are rendered by the server and which are rendered by the client is critical to using RSC effectively.
Use Suspense with RSC: Suspense allows you to handle promises in your React components, which is integral to the functioning of RSC
Optimize for performance: RSC can significantly improve page load performance and reduce bundle size. However, it's important to measure and monitor these metrics to ensure that you're getting the expected benefits.
Consider the tradeoffs: While RSC offers many benefits, it also comes with tradeoffs. For example, server components cannot use states or effects, which can limit their use in certain scenarios.
Experiment with RSC in non-critical parts of your application: Given the experimental nature of RSC, it may be a good idea to start experimenting with it in non-critical parts of your application. This will give you a better understanding of how it works and how it can be used effectively.
Combine RSC with other React features: RSC can be combined with other React features like Suspense and Concurrent Mode to build more efficient and user-friendly applications.
Examples.
Let's look at some code examples to illustrate how React Server Components (RSC) work.
First, let's define a server component. Server components are identified by the .server.js extension:
// Message.server.js
import {db} from './db.server';
function Message({id}) {
const message = db.messages.get(id);
return (
<div>
<h1>{message.title}</h1>
<p>{message.body}</p>
</div>
);
}
export default Message;
In this example, Message is a server component that fetches a message from a database based on an id prop. Notice that we're directly importing a server module (db.server) and using it to fetch data. You can't do this in a client component.
Next, let's define a client component that uses this server component:
// App.client.js
import {useState} from 'react';
import Message from './Message.server';
function App() {
const [selectedId, setSelectedId] = useState(1);
return (
<div>
<button onClick={() => setSelectedId(selectedId + 1)}>
Next
</button>
<Message id={selectedId} />
</div>
);
}
export default App;
In this example, App is a client component that maintains a piece of state (selectedId) and renders the Message server component with the current id. When the Next button is clicked, the selectedId is incremented, causing the Message component to be rendered again with the new id.
This is a simple example, but it illustrates the key idea behind RSC: server components can be used to fetch and render data on the server, while client components maintain interactivity on the client.
Comparison
While both SSR and RSC involve rendering on the server, they serve different purposes and have different tradeoffs:
SSR is primarily concerned with improving performance and SEO by sending static HTML to the client. However, it requires sending all JavaScript to the client, which can be large and slow to parse.
RSC, on the other hand, allows the server and client to collaborate on rendering, which can reduce the amount of JavaScript sent to the client and improve performance. However, server components cannot use states or effects, which can limit their use in certain scenarios.
In summary, both SSR and RSC are powerful techniques for rendering React applications, each with its own strengths and tradeoffs. Understanding these differences can help you make more informed decisions about which technique to use in your projects.
Links:
https://react.dev/blog/2022/03/29/react-v18
https://react.dev/blog/2020/12/21/data-fetching-with-react-server-components
https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#react-server-components
https://shopify.github.io/hydrogen-v1/tutorials/react-server-components
https://www.plasmic.app/blog/how-react-server-components-work
Top comments (14)
Is not prohibited to import a Server Component into a Client component?
In
<App>
you import<Message>
.It is possible to do both ways. You just need to make sure to keep state out of Server components and also you need to make sure to write the right directive at the beginning of the file.
A good example is when you have a context provider in your application. The recommendation is to put it as close as possible in your component tree, but just because you use a Theme Context or a Authentication Context it doesn't mean that the rest of the components in the tree cannot be server components. Hope that explains. Cheers
Before reading this post, that shows this implementation :
my idea to do the same is to implement with cookies as communication between server and client tree.
Like so:
Are both ok ?
Is there a risk of exposing server stuff with first approach ?
Edited: added
revalidateRoute() in click handler
Both approaches are possible, but they have different tradeoffs:
As for exposing server stuff, RSCs are designed to be secure. They run only on the server and have no direct access to the client. Any data returned by a server component is included in the HTML response, much like any other data included in an HTTP response.
Modify the App.client.js file to use cookies:
In this example, we'll use the
useState
anduseEffect
hooks to read the initialselectedId
value from a cookie and update the cookie whenever theselectedId
changes.Note that this is a simplified example. In a real application, you would want to use a more robust method for managing cookies, such as the js-cookie library.
This is from Next.js docs:
I was assuming that this behavior is a React thing and not a Next thing.
There are some place where this is explained from a plain React side?
Hi, Importing server components into a client component depends on the context and technology you are using. Typically, server components contain business logic and single CPU, databases and other sensitive data, and importing them directly into a client component can pose potential risks to the security and efficiency of the application.
RSC is currently on experimental stage.
Some APIs and rules is changing and may be changed in the future.
It's worth mentioning this reference, which is maintained officially by React team.
github.com/reactjs/rfcs/blob/main/...
Btw, React team and Next.js team is collaborating closely, improve RSC and ship it ASAP as a playground for community before this feature is officially stable .
nextjs.org/docs/getting-started/re...
Nice illustrations! Framing them would've been great to see! We've built a simple OSS tool to help with screenshots. Check it out and let us know what you think! github.com/ekqt/screenshot I'd appreciate giving it a Star on GitHub if you find it helpful! Cheers!
thanks for the post! I haven't found explanation for why we don't have "await" to get a db record here:
Can we use react hooks like (useState or useEffect) inside Message.server.js file?
No, those are features for client components.
So React js is just copying Next js.
NextJS IS a React framework ;) they aren't copying, but rather implementing it.