As the stable releases of React 19 and Next.js 15 approach, the React team introduces one of the most significant changes to React’s architecture: the React Server Components (RSC) paradigm, which differentiates between Server Components and Client Components.
Server Components run only on the server and have zero impact on bundle-size. Their code is never downloaded to clients, helping to reduce bundle sizes and improve startup time.
In contrast, Client Components are the typical components you’re already used to. They can access the full range of React features: state, effects, access to the DOM, etc.
This new approach aims to leverage the strengths of both server and client environments, optimizing for efficiency, load times, and interactivity.
And while it brings substantial improvements and flexibility to React development, it also introduces concepts that can be easily misunderstood. In this post, we’ll address five common misconceptions about React Server Components (RSC) to help clarify some of the finer points of this new architecture.
You might think that to get the most benefit from the new RSC architecture, you should convert all the components to Server Components and use Client Components sparingly. However, the optimal approach is actually more balanced:
- While Server Components offer many benefits, they aren’t suitable for all scenarios, especially those requiring interactivity. React Server Components are a new type of React component that operates exclusively on the server, fetching data and rendering entirely on the server to improve performance.
// UserProfile.js (Server Component)
// This component can directly access the database or API
// without exposing sensitive information to the client
async function UserProfile({ userId }) {
const response = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
const user = response.user;
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
<p>Location: {user.location}</p>
<LikeButton initialLikes={user.likes} />
</div>
);
}
- Client Components are essential for features that need client-side interactivity, state management, or browser APIs.
// LikeButton.js (Client Component)
// This component can handle interactivity
'use client'
import { useState } from 'react';
function LikeButton({ initialLikes }) {
const [likes, setLikes] = useState(initialLikes);
const handleLike = () => {
setLikes(likes + 1);
};
return (
<button onClick={handleLike}>
Like ({likes})
</button>
);
}
- The recommended practice is to place client components as low as possible in the component tree, ideally making them leaf components.
The introduction of Server Components doesn’t mean they’re inherently better than Client Components or that they should always be preferred. Both have their place in a well-architected React application. Client Components remain crucial for creating interactive, dynamic user interfaces, and using them where appropriate is not only acceptable but often necessary.
Remember, the goal of this new architecture is to provide more tools for building efficient React applications, not to replace Client Components entirely. Use Server Components where they make sense, and don’t hesitate to use Client Components where they’re the best fit for your needs.
You might assume there’s a symmetry in how we declare Server and Client Components. The new architecture is designed to support React Server Components, allowing them to run exclusively on the server and leverage server-side rendering benefits. The reality is a bit different:
use client
does indeed mark a boundary for Client Components.
// LikeButton.js - 'use client' lets you mark what code runs on the client
'use client'
function LikeButton({ initialLikes }) {
// Rest of the component code
}
- However, there is no corresponding
use server
directive for marking Server Components. Server Components are the default in Next.js App Router and React Server Components paradigm.
// UserProfile.js - Server Component by default
async function UserProfile({ userId }) {
// Rest of the component code
}
It’s worth noting that a use server directive does exist, but it serves a different purpose. It’s used for marking server actions, not for declaring Server Components.
At first glance, you might assume there’s a clear-cut separation between where Server and Client Components render. Traditional React components typically render on the client side, but with the introduction of Server and Client Components, the rendering process has become more nuanced:
- Server Components do indeed render on the server.
- However, Client Components are more versatile than many realize. They can render on both the server (for the initial page load) and the client.
- The key distinction lies in their capabilities: Client Components can use client-side features such as state, effects, and event handlers, whereas Server Components cannot.
Relying solely on client-side JavaScript for rendering web pages can lead to significant drawbacks. Users would initially see a blank page until their browser downloads, processes, and runs the JavaScript code, resulting in delayed content visibility. This delay can make sites feel slow and unresponsive, particularly on slower networks or less powerful devices. Moreover, it can hinder search engine indexing, potentially impacting a site's visibility in search results.
To optimize this, you can render Client Components on the server for the initial page load. This pre-rendered HTML allows users to see the content immediately, while React works in the background to make components fully interactive, without any visible change to the user. This approach significantly improves both the perceived loading speed and the actual performance of your application.
You might think that each component with any client-side behavior needs its own use client
directive. However, this isn’t the case:
- You only need to add
use client
at the top of the file for the topmost component that requires client-side features. - All components defined in that file automatically become Client Components.
- This includes nested components, utilities, and functions within the file.
In the component tree below, FormatText.js
, originally a server component, is executed client-side when imported into Dashboard.js
, which is a client component marked by the use client
directive. (White color represents server components, and blue color represents client components.)
Why is this important to understand?
Since JavaScript modules can be shared between both Server and Client Components, it’s possible for code intended only for the server to end up running on the client.
Consider this data-fetching function:
export async function getData() {
const res = await fetch('https://external-service.com/data', {
headers: {
authorization: process.env.API_KEY,
},
})
return res.json()
}
While it appears to work on both server and client, it contains an API_KEY
meant for server-side use. If this function is imported into a Client Component, it could expose sensitive information. By keeping sensitive data and logic, such as tokens and API keys, on the server, Server Components enhance the security of your application.
Next.js replaces private environment variables (those not prefixed with NEXT_PUBLIC
) with an empty string on the client to prevent leaks. This means getData()
won’t work as expected if executed on the client.
Understanding these nuances helps you structure your application more securely and efficiently. By being mindful of where your code runs, you can prevent sensitive information leaks and ensure your components behave as intended, whether they’re running on the server or the client.
You might think that once you’ve crossed into Client Component territory, you can’t go back and therefore server components cannot be nested inside client components. But there’s more flexibility than you might expect:
- While you can’t directly nest Server Components inside Client Components, you can pass Server Components as props to Client Components. This approach allows for a seamless integration of client and server components, optimizing both performance and interactivity.
- One common pattern is known as the “children as props” pattern. Here’s a simple example:
// ServerComponent.js
function ServerComponent() {
return <h1>Hello from the server</h1>;
}
// ClientComponent.js
'use client'
function ClientComponent({ children }) {
return (
<div>
<p>This is a client component</p>
{children}
</div>
);
}
// Usage
function App() {
return (
<ClientComponent>
<ServerComponent />
</ClientComponent>
);
}
This flexibility allows you to create more efficient applications by keeping data-fetching and heavy computations on the server while still providing interactivity where needed. It’s like having the best of both worlds - the power of server-side operations with the responsiveness of client-side interactivity.
The introduction of React Server Components doesn't mean you need to radically change how you build React apps overnight. Instead, see it as a powerful new tool in your developer toolkit, one that you can gradually adopt and experiment with to optimize your React applications.
Introducing Visual Copilot: convert Figma designs to high quality code in a single click.