• Docs
  • Login
Talk to an expertTry for free
Blog
Blog
BlogProductCase studiesNewsInsights
Blog

Master React server components with our comprehensive guide

JavaScriptnode.jsperformance
Updated: 10 July 2026
Share
This post is also available in German and in French.

React has completely transformed how we create user interfaces by introducing a component-based design that encourages efficiency and reusability. Its declarative method and component-based structure have gained popularity among developers. But as your projects grow in complexity, you might encounter performance challenges because of rendering and data-retrieval methods.

In the traditional methods of rendering on the client side, you depend on the browser to run JavaScript for showing the initial user interface. This may cause delays in loading on devices with limited resources. Fetching data from an application programming interface (API) introduces additional load on these devices, which can cause a lag in displaying content and providing less-than-ideal user interactions.

Server components offer a solution to these obstacles by enabling the rendering of components on the server. This allows the server to stream the components to the client side, which enhances the performance and the data-retrieval processes since all the processing happens on the server and not on the users' devices.

In this article, you will learn how React's rendering methods have evolved over time, as well as the drawbacks of using React Suspense and server-side rendering (SSR). Additionally, you'll learn about React Server Components (RSCs) and how they tackle these issues.

Understanding traditional React rendering strategies

Before getting into RSCs, it's helpful to understand earlier rendering strategies, such as React Suspense and SSR.

React Suspense

React Suspense was introduced to handle asynchronous rendering in React applications. It allows components to "suspend" rendering until certain conditions are met, such as data fetching or code splitting. Here are some of the advantages of Suspense:

  • Improved user experience. Suspense allows you to have placeholders for your components, which makes it easy to show loading indicators when data is being fetched. This helps in avoiding blank content showing on the user's screen.
  • Component rendering. When you have components fetch data from an API, Suspense pauses the rendering of the component until the data is available. This prevents partially loaded pages and helps users have a better experience.
  • Code splitting. Suspense works with features like React.lazy to enable code splitting and improve performance by loading components only when they're needed.

But React Suspense has some limitations to be aware of as you scale your application:

  • Incomplete support for server-side data fetching. Suspense makes client-side data loading easier, but it doesn't support data fetching on the server side. To manage data retrieval from the server side, you need to use tools such as React Query. For instance, with Suspense, you still have to handle tasks like server-side caching and make client-side API requests to fetch data, which can add complexity to SSR.
  • Increased complexity with nested components. Working with Suspense in nested components can get quite complex at times. For example, when you're developing a product page that displays reviews, recommendations, and stock information, the coordination between loading the data for these nested components with Suspense boundaries can lead to slower loading times. This can complicate the code as each component's data dependencies and loading states must be managed.
  • Boilerplate code overhead. Suspense often requires extra code to handle loading states, errors, and fallbacks. For example, in a large app, you might end up wrapping many components in Suspense and ErrorBoundary, which adds repetitive code and makes it harder to keep everything consistent and maintainable as your app grows.

Server-side rendering

When it comes to SSR, React components are rendered on the server, and then the fully generated HTML is sent to the client's side for display. In contrast, with RSCs, individual components are rendered when they are ready instead of waiting for the whole application to load, as is the case of SSR. This approach has multiple advantages:

  • Improved initial load times. By pre-rendering the HTML on the server, you allow the browser to show content much faster. For example, if you're building an e-commerce site, users can see product details right away while the rest of your JavaScript loads in the background. This makes your app faster, especially if the users have a slow connection or device.
  • SEO advantages. Search engines can easily categorize the rendered HTML code of your website, which enhances its visibility in search results. For instance, if you have a blog or a content-rich website, using SSR assists in guaranteeing that search engines promptly access and evaluate your articles without delay for JavaScript loading. In this case, SSR enables you to have a better SEO ranking.

SSR also has limitations to be considered:

  • Hydration overhead. After the server sends the initial HTML, the browser has to "hydrate" it by attaching event listeners and making the page interactive. This can cause performance issues because the browser has to render the page twice—once for the static content and again for the interactive parts. For example, if applications have features like analytics dashboards with graphs and input fields, this multiple rendering of different components can cause delays for users.
  • Larger payloads. With SSR, you have to send the entire JavaScript bundle to the client, which can increase the payload size. For example, when you're building a large app with multiple features, the amount of JavaScript sent to the browser can increase significantly, which may slow down page load times, especially on slower connections.
  • Resource-intensive. SSR has to generate HTML for each request made to it, leading to increased processing requirements and elevated server expenses. For example, if your application faces a traffic surge, you may need to boost server resources, adding complexity and cost to infrastructure management.

React Server Components

RSCs are a new type of component in React that operates on the server side, unlike traditional components, which operate on the client side. They help address the issues associated with Suspense and conventional SSR by enabling components to be processed and streamed as HTML from the server to the client. RSCs also handle data retrieval on the server, ensuring that confidential information, such as API keys, is not revealed to the client.

RSCs provide some significant advantages:

  • Optimized performance. With RSCs, you move the rendering work to the server, reducing the amount of JavaScript your users' browsers need to run. For example, if you're building a blog, HTML content can be streamed directly, allowing posts to appear almost immediately without waiting for JavaScript processing.
  • Secure data fetching. RSCs fetch data server-side, enhancing both speed and security. For example, in an e-commerce app, the server retrieves product data, keeping API keys hidden from the client.
  • Reduced client-side JavaScript execution. Offloading rendering to the server reduces JavaScript execution on user devices, enhancing performance, especially on slower devices. In a dashboard, for instance, the server processes data-heavy components, so minimal JavaScript is needed on the client side.
  • Faster initial page loads. Since RSCs stream pre-rendered HTML, content loads quickly for users. On a news site, articles and images display instantly, avoiding blank screens while JavaScript loads.

RSCs vs. traditional React components

Compared to client-side components, RSCs handle execution, data management, and performance in different ways.

Traditional components run in the browser (client-side), where JavaScript is needed to display the user interface (UI). RSCs run on the server by sending pre-rendered HTML to the client device instead of processing everything locally. This approach lessens the workload on the user's device and improves loading speed. Handling layouts and static content server-side is also ideal for noninteractive or data-driven components. Client components can be nested within RSCs where interactivity is needed, such as for buttons or forms, while keeping sensitive data secure on the server.

Traditional components can increase the size of the client-side JavaScript bundle, which could lead to slower app performance. RSCs do not increase the client's package size as they operate on the server side, leading to reduced downloads and quicker page loading times. You can also use RSCs to improve caching. Server-rendered content is easier to cache, which speeds up responses for frequently accessed data, like popular products in an online store, without repeatedly querying the database.

How to implement RSCs

The simplest way to use RSCs in practice is through the Next.js App Router, which has built-in support for React Server Components without requiring manual webpack or Babel configuration.

Note that the code examples in this section use Next.js 15 with React 19.

Project setup

Ensure you have Node.js 22 (current LTS) installed, then create a new Next.js project:

npx create-next-app@latest your-app
cd [your-app]

When prompted, select the App Router option. This is important because RSCs are only available in the App Router, not the legacy Pages Router.

Creating your components

In the Next.js App Router, every component is a server component by default. Create a file at app/components/UserList.jsx:

// app/components/UserList.jsx
// This is a server component by default - no directive needed


async function UserList() {
  // Data fetching happens on the server
  // API keys and sensitive data never reach the client
  const users = [
    { id: 1, name: "John Doe" },
    { id: 2, name: "Jane Smith" },
    { id: 3, name: "Alice Johnson" },
  ];


  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}


export default UserList;


To add client-side interactivity, you must explicitly mark a component with the "use client" directive. Create a file at app/components/Counter.jsx:

// app/components/Counter.jsx
"use client"; // This directive marks it as a client component


import { useState } from "react";


function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);


  return (
    <div>
      <button onClick={() => setCount((prev) => prev + 1)}>
        Increment
      </button>
      <p>Count: {count}</p>
    </div>
  );
}


export default Counter;

Now update app/page.jsx to combine both components:

// app/page.jsx
// This is a server component - it can import both server and client components


import UserList from "./components/UserList";
import Counter from "./components/Counter";


export default function Page() {
  return (
    <div>
      <h1>Users</h1>
      <UserList />        {/* Rendered on the server */}
      <Counter initialCount={50} />  {/* Interactive, runs on the client */}
    </div>
  );
}

 

Run and test the code

Start the development server:

npm run dev

 

Your app will be running at http://localhost:3000. The UserList component renders on the server and streams HTML to the client, while the Counter component hydrates in the browser and handles interactivity locally.

The key difference from traditional React is that UserList sends no JavaScript to the browser — only HTML. This reduces your client-side bundle size and improves load performance automatically.

Best practices for using RSCs

Since RSCs handle most of the rendering on the server, shifting as much of your app's rendering as possible to the server can significantly improve performance while keeping interactive features intact where needed. The following are some best practices that can help you achieve this and enhance your application's speed, flexibility, and manageability with RSCs.

First, use RSCs to fetch data from APIs or databases. If you've already fetched data on the server, don't fetch it again on the client. For example, if you load user data on the server and pass it to the client, there's no need for a second API call on the client side unless the data changes.

Breaking code down into small chunks and selectively loading the needed scripts using tools such as Vite or webpack can help improve load times. Create components that can be reused across your app. For example, if multiple pages need to display a list of users, create a UserList component that works everywhere.

Finally, keep server components lightweight by focusing them purely on rendering data as they don't need to manage state like client components do. Focus on making them lightweight and purely responsible for rendering data. Only hydrate the parts of a page that need to be interactive. For example, if the page is mostly static but has a form or interactive button, hydrate only those components.

Challenges and limitations of RSCs

Although RSCs come with benefits, it's also important to understand the obstacles and boundaries that come with incorporating them into your project.

State management

Server components cannot interact with lifecycle methods such as useEffect or useState. This means you can't manage client-side interactivity within server components. For example, if a form is rendered server-side, you'll need a client component to handle real-time input changes.

When you're dealing with both client- and server-side components in your projects, it's important to handle the interaction between these two types of components across your application to avoid any issues. For example, if your application fetches data from the server and lets users edit this data locally on their devices, you might face issues with keeping the data consistent between the server and the client-side components.

Non-serializable code

Server components can only use serializable props, meaning you can't pass functions, symbols, or any complex objects that can't be serialized. For instance, sending a callback function from a server component to a client component will lead to an error message. Server-side components don't have access to browser APIs such as window, document, or localStorage. If you need to interact with the DOM, that must happen in a client component.

Hydration mismatches

If the HTML displayed by the server doesn't align with what the client-side JavaScript expects, it may lead to problems during the hydration process. For example, differences in how dates are formatted on the server vs. the client can cause subtle mismatches. Hydration mismatches can be hard to debug because the root cause isn't always obvious.

Third-party library compatibility

Some third-party libraries may not work well with RSCs, particularly those that heavily depend on client-side rendering techniques or browser specific APIs. For example, libraries such as react-router-dom require client side rendering. You may need to replace or adapt some libraries to make them work with RSCs.

Solutions for common RSC issues

There are practical solutions you can implement to maintain consistency and address common RSC issues.

Use consistent state management

Use state management tools that work across both server and client, like Redux or Zustand. These libraries allow you to centralize your state so that server-rendered components and client-side components can access the same data. For example, you could use Redux to store a global state that's available to both sides of the app.

Ensure that you maintain a flow of data between the server and client by sharing context. For instance, when moving user data between server and client components, make sure the information is managed centrally to avoid any discrepancies.

Use non-serializable code and refactor components

Consider using development tools or code linters to identify non-serializable properties that might cause issues with RSCs. For instance​, tools such as ESLint can be set up to detect function props that are transmitted from server-based components.

Make sure to refactor components to ensure that props can be serialized and avoid any issues with SSR. One way to do this is by moving certain operations like event handlers to client components. For example, when passing a callback function from a server component, make sure the code is adjusted so that the operation happens within the client component where it's being used.

Handle hydration mismatches

Addressing hydration mismatches requires proactive testing and debugging techniques:

  • Strict mode. Turn on React's strict mode to catch problems in the development process. Strict mode will point out errors as you work so you can fix them before they become issues in the final product.
  • Thorough testing. Ensure thorough testing plans are in place to detect any problems related to hydration. Test automation can mimic server-side and client-side rendering processes to maintain uniformity. Consider using tools such as Cypress or Jest to confirm that the output displayed is consistent across both server and client environments.

Ensure third-party library compatibility

If third-party software does not work with server components as needed for your project's requirements, explore alternative options that are server compatible. If you use an open source library for your project, you might want to think about giving back by contributing to its development to include RSC support as well. By contributing, you can enhance your own application and also have a positive impact on the broader community that relies on the same library for their work.

By tackling these challenges with proper strategies in place, you can efficiently navigate the limitations of RSCs and build robust, performant applications.

Conclusion

In this article, we explained React's rendering techniques. We introduced React Suspense and SSR and provided a more in-depth guide to RSCs. You can use RSCs to help enhance the performance of your application by decreasing the need for client-side JavaScript processing and accelerating the initial page loads.

If you want to try out RSCs in your projects, the first step is to pinpoint the components in your app that would gain advantages from SSR. You can then slowly incorporate RSC into your codebase.

Running the infrastructure for SSR and server components can be complex. Using Upsun, a platform as a service (PaaS), simplifies deploying and scaling your React applications. With its built-in Node.js support and automatic scalability, Upsun can help you handle traffic surges smoothly. It also provides monitoring and security tools and allows you to manage multiple applications or microservices in different languages within a single project. This way, you can focus on building your React application while Upsun handles the core infrastructure.

Stay updated

Subscribe to our monthly newsletter for the latest updates and news.

Your greatest work
is just on the horizon

Free trial