2024-05-19
React Server Components (RSC) represent a shift in how we think about rendering in React. Traditionally, React components run in the browser. With server components, some components run only on the server, and their output is sent to the client as a rendered result. This changes the architecture of React applications in a meaningful way.
In the RSC model, components are server components by default. They run on the server, have access to the file system, databases, and other server-side resources directly. They do not ship any JavaScript to the client, which reduces the bundle size.
Client components are components that need interactivity. If a component uses useState, useEffect, event handlers, or browser APIs, it needs to be a client component. We mark them with the "use client" directive at the top of the file.
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}A server component can import and render a client component, but a client component cannot import a server component directly. It can, however, receive server components as children through props.
One of the biggest advantages of server components is data fetching. Instead of fetching data on the client with useEffect and managing loading states, we can fetch data directly in the component using async/await:
async function UserProfile({ userId }) {
const user = await db.users.findById(userId);
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}There is no need for loading spinners, no client-side fetch calls, and no exposing API endpoints just to get data to the UI. The data is fetched on the server and the rendered HTML is sent to the client.
Since server components do not send JavaScript to the browser, we can use large libraries on the server without affecting the client bundle. For example, we might use a markdown parser or a syntax highlighter in a server component, and the client never downloads those libraries. Only the rendered output reaches the browser.
Next.js App Router is built around React Server Components. In the app directory, every component is a server component by default. Pages can be async functions that fetch data directly. Layout components wrap pages and can also fetch their own data. This model encourages thinking about which parts of the UI actually need interactivity and keeping those as small client components while the rest stays on the server.
The mental model takes some adjustment. We need to think carefully about the boundary between server and client components. State cannot be shared between them in the traditional React way. Serialisation constraints apply, meaning we can only pass serialisable data from server to client components. And debugging can be trickier because errors might happen on either side of the boundary.