React Server Components (RSC)
React Server Components (RSC) is a new component type that allows components to be rendered in a server environment, bringing better performance and developer experience to modern web applications.
Core Advantages
- Zero Client JavaScript: Server Components code is not bundled to the client, significantly reducing client bundle size
- Direct Access to Server Resources: Can directly access server resources such as databases, file systems, and internal APIs without additional API layers
- Better Performance: Data fetching is closer to the data source, reducing client-side data waterfalls and improving first-screen load speed
- Automatic Code Splitting: Code splitting based on actual rendered data, not just routes, enabling more granular code optimization
- Higher Cohesion: Logic closely related to data, permissions, caching, etc., can remain in Server Components, improving component cohesion and reducing state lifting and cross-level passing
Before starting, we recommend reading React's official Server Components documentation to get a basic understanding of Server Components.
Quick Start
-
Ensure React and React DOM are upgraded to version 19 (recommended version 19.2.4 or above)
-
Install the
react-server-dom-rspack@0.0.1-beta.0dependency
- Currently, Server Functions are not supported in SPA projects
- Currently, when building with Rspack, the output chunks and bundle size are not yet optimal. We will further optimize this in the near future
- Set
server.rsctotrue:
If you have a CSR project that uses Modern.js Data Loaders, after enabling RSC, Data Loaders will execute on the server by default. For detailed migration guide, please refer to CSR Project Migration to RSC.
Usage Guide
Default Behavior
By default, when RSC is enabled, all components in Modern.js are Server Components by default. Server Components allow you to fetch data on the server and render UI. When you need interactivity (such as event handling, state management) or use browser APIs, you can use the "use client" directive to mark components as Client Components.
Component Type Selection
When to Use Client Component
When a component needs the following features, you need to use the "use client" directive to mark it as a Client Component:
- Interactivity: Using State and event handlers, such as
onClick,onChange,onSubmit - Lifecycle: Using lifecycle-related hooks, such as
useEffect,useLayoutEffect - Browser APIs: Using browser APIs (such as
window,document,localStorage,navigator, etc.) - Custom Hooks: Using custom hooks, especially those that depend on client-side features
When to Use Server Component
The following scenarios should use Server Components (default behavior, no additional marking required):
- Accessing Server Resources: Using APIs available only on the server (such as Node.js APIs, file systems, consul, RPC, etc.)
- Data Fetching: Fetching data on the server to optimize performance and reduce client requests
- Security: Accessing private environment variables or API keys, avoiding exposure to the client
- Reducing Bundle Size: Using large dependency libraries that don't need to be included in the client bundle
- Static Content: Rendering static or infrequently changing content
Client Boundary
Once a file is marked with "use client", all other modules it imports (if they haven't been marked with "use client" yet) will also be considered client code and included in the client JavaScript bundle. This is the concept of Client Boundary.
The "use client" directive creates a boundary: all code within the boundary will be bundled to the client. This means that even if the Button and Tooltip components don't have the "use client" directive themselves, they will become client code because they are imported by InteractiveCard.
How to Combine Both Component Types
Server Components and Client Components don't exist in isolation; they need to work together. Remember the following two rules:
Server Component Can Import Client Component
This is the most common pattern. Your page body is a Server Component responsible for data fetching and layout, while interactive parts are embedded as Client Components.
Client Component Cannot Directly Import Server Component
This may seem counterintuitive at first. The reason is that Server Component code doesn't exist on the client at all. When a Client Component renders in the browser, it cannot execute a function that only exists on the server.
However, there are two patterns to work around this limitation:
1. Pass Server Component via children Prop
You can pass Server Components as the children Prop to a Client Component. For example, an animated Tabs component where the tab switching logic is client-side, but the content of each tab might be static and fetched from the server.
Through this pattern, you can keep components on the server to the maximum extent while maintaining interactivity, achieving optimal performance. This is one of the most powerful composition patterns in RSC.
2. Route Components Can Independently Choose Component Type
Each level of route components (such as layout.tsx, page.tsx) can independently choose to be a Client Component or Server Component:
For example, if layout.tsx is a Client Component (requiring client-side interactivity), you can still set page.tsx as a Server Component (for data fetching and rendering). This approach provides great flexibility and allows non-RSC projects to gradually migrate to RSC projects.
Server Component and Data Loader
In RSC projects, you have two ways to fetch data: directly in Server Components, or using Data Loader. Both approaches have their advantages, and you can choose flexibly based on your scenario.
Comparison of Two Data Fetching Approaches
Generally, we recommend fetching data in Server Components, because waterfall requests have less performance impact on the server, and this approach makes components more cohesive, with data fetching logic and UI rendering in the same place, making it easier to understand and maintain. However, if your page has multiple independent data sources and you want to completely avoid request waterfall issues, Data Loader's parallel execution feature will be more advantageous.
Data Loader Execution Environment in RSC Projects
In RSC projects, the execution environment of Data Loaders is related to file naming:
*.data.ts: Executes only on the server, and data can be consumed by both Client Components and Server Components*.data.client.ts: Executes only on the client
In Modern.js RSC projects, Server Components can receive data returned by Data Loaders through the loaderData prop:
Data Loader Returning Server Component
In Modern.js RSC projects, Data Loaders have a powerful feature: they can return Server Components. This is very helpful for gradual migration, allowing you to render server-generated content in Client Components.
CSR Project Migration Guide
Modern.js's RSC capability supports both SSR and CSR projects. For existing CSR projects, if you want to gradually migrate to RSC, we recommend following these steps:
- Enable RSC Configuration
- Mark All Route Components with
'use client'
This ensures that existing components maintain their behavior and continue to run as Client Components.
- Rename All
*.data.tsto*.data.client.ts
Since *.data.ts in RSC projects executes on the server by default, to maintain consistency with CSR project behavior (Data Loaders execute on the client), you need to rename the files.
After completing these steps, you can gradually migrate components to Server Components and enjoy the performance benefits of RSC.
Notes
Projects Using Streaming SSR
If you're using both Streaming SSR and RSC, in React 19 you need to use use instead of the Await component:
Best Practices
Data Fetching
- Whether it's an SSR or RSC project, it's recommended to use the
cachefunction provided by Modern.js for data fetching logic executed on the server by default. This ensures that for each server-side render, no matter how many times the function is called, it will only execute once.
This is also the recommended usage by React.js, which provides the cache function. Modern.js's cache can be considered a superset of it.
- Based on using the
cachefunction, you no longer need to manage server-side state throughprops,context, etc. We recommend fetching data in the nearest Server Component where it's needed. With thecachefunction, even if the same function is called multiple times, this makes project state management, business logic, and performance optimization simpler.
Optimal Performance
To leverage the advantages of RSC or Streaming SSR, we need to make as many components as possible flow. A core principle is to make the area wrapped by Suspense as small as possible (this is also one of the reasons we recommend using the cache function).
For Server Components that directly consume data, we recommend wrapping them with Suspense at a higher level:
In this scenario, Server Components are often asynchronous. There's another case where Server Components are synchronous and data is consumed by Client Components, described below.
There's another scenario where data is consumed in Client Components. In this case, we should avoid using await in Server Components to avoid blocking rendering:
Helmet
When using React 19, you no longer need to use Helmet. We recommend directly using the components provided by React.
Common Issues
This entry point is not yet supported outside of experimental channels
The project's bundle has introduced a non-19 React version, commonly seen in monorepos. Please ensure all dependencies use React 19.