Hubert
10 min
March 26, 2025

How to understand React Server Components?

React Server Components (RSC) is a new feature in the React ecosystem that allows React components to be rendered exclusively on the server side. Unlike traditional client components, which are rendered in the user's browser, server components are processed on the server and the resulting HTML is sent to the client. This approach brings a number of benefits, such as reducing the size of transmitted JavaScript packets, improving application performance and increasing security by keeping sensitive operations on the server side.

Read more
How to understand React Server Components?
Schedule a free consultation

    We process your data in accordance with our privacy policy.

    What does “rendering” really mean in React?

    In the classic sense, “rendering” means the transformation of HTML and CSS into visible pixels on the screen by the browser. React, however, uses the term differently – as the process of calling component functions to build the structure of the Virtual DOM (DOM), the internal representation of what the interface should look like.

    This means that when we talk about “rendering” in React, we mean calculating what the JSX result should be in the form of a tree of React elements – even before anything is drawn on the screen. The “painting” (paint) of the interface itself happens only later, after the virtual tree is compared with the existing DOM and changes are applied.

    Understanding this distinction between a “renderer” in React and a classic browser renderer is crucial to correctly interpret how Server Components work.

    Context and evolution

    From the beginning, React was a client-side rendering library. As the complexity of the application increased, the need for more efficient data transfer, better loading optimization and security began to emerge. The initial answer to these needs was Server-Side Rendering (SSR), which, however, still required client-side hydration. React Server Components were designed to complement these solutions, allowing even more control over what goes into the user’s browser.

    React Server Components basics

    Server components allow direct access to server resources, such as databases or the file system, without the need for additional APIs. Below is an example of a simple server component that retrieves data from a database:

    import db from 'imaginary-db';
    
    async function ServerComponent() {
      const data = await db.query('SELECT * FROM table');
    
      return (
        <div>
          {data.map((item) => (
            <p key={item.id}>{item.name}</p>
          ))}
        </div>
      );
    }
    
    export default ServerComponent;
    

    In this example, the ServerComponent performs a database query and renders the results. The whole process takes place on the server, and the client receives the already generated HTML. It is worth adding that direct SQL queries in the component are not recommended. A good practice would be to use an abstraction layer (e.g. ORM – Prisma, Drizzle).

    Componentization of the backend

    React Server Components can be thought of as a new form of building backend logic – component-based, declarative and integrated into the UI. Instead of creating separate API endpoints and managing their state and responses on the client side, a server component can directly communicate with the database or backend services, and pass the result to the UI as finished HTML.

    This approach leads to much tighter integration between backend logic and UI components and reduces the need for intermediate layers such as REST or GraphQL – especially in applications developed by a single front-back team. This allows a server component to act as a controller, data source and view template at the same time, bringing the architecture closer to a “server-first UI” model. This means less fragmentation of responsibility and easier code maintenance in fullstack projects.

    Do you need support with IT projects?
    Do you need support with IT projects?
    Do you need support with IT projects?
    Contact us!

    What are the differences between server and client components?

    In the new React paradigm, all components are treated as server by default. To mark a component as client, add the ‘use client’ directive at the beginning of the file. Example:

    'use client';
    import React, { useState } from 'react';
    function ClientComponent() {
      const [count, setCount] = useState(0);
      return (
        <button onClick={() => setCount(count + 1)}>
          Clicking {count} times
        </button>
      );
    }
    export default ClientComponent;
    

    Client components are necessary to handle user interactions, such as state management (useState) or side effects (useEffect). Server components, on the other hand, do not have access to hooks and are rendered only once – on the server. They are not re-rendered on the client side and do not respond to events, which means they cannot be used to support interactive functionality.

    Hybrid architecture and component nesting

    Server components can include client components – this allows you to create an application structure in which data is retrieved and processed on the server, and interactions are handled on the client side.

    Example:

    Server Component (ServerComponent.jsx):

    import ClientButton from './ClientButton';
    async function ServerComponent() {
      const data = await fetch('https://api.example.com/data').then(res => res.json());
    
      return (
        <div>
          <h2>Dane z API:</h2>
          <ul>
            {data.map((item) => (
              <li key={item.id}>{item.name}</li>
            ))}
          </ul>
          <ClientButton />
        </div>
      );
    }
    
    export default ServerComponent;
    

    Client component (ClientButton.jsx):

    'use client';
    
    import { useState } from 'react';
    
    export default function ClientButton() {
      const [clicked, setClicked] = useState(false);
    
      return (
        <button onClick={() => setClicked(true)}>
          {clicked ? 'Clicked!' : 'Click me'}
        </button>
      );
    }
    

    In some situations, a server component can be “injected” into a client component. Example:

    // ServerComponent.tsx (Server Component)
    export default function ServerComponent() {
      // Server-side data fetching or logic
      const data = fetchDataOnServer();
    
      return (
        <div>
          <h2>Server Component</h2>
          <p>{data}</p>
        </div>
      );
    }
    
    // ClientComponent.tsx (Client Component)
    'use client';
    
    import { useState } from 'react';
    
    export default function ClientComponent({ children }) {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <h1>Client Component</h1>
          <button onClick={() => setCount(count + 1)}>
            Count: {count}
          </button>
    
          {/* Server Component is rendered here */}
          <div className="server-content">
            {children}
          </div>
        </div>
      );
    }
    
    // Page.tsx (Parent Server Component)
    import ClientComponent from './ClientComponent';
    import ServerComponent from './ServerComponent';
    
    export default function Page() {
      return (
        <ClientComponent>
          <ServerComponent />
        </ClientComponent>
      );
    }
    

    The above example shows the recommended pattern for passing a server component to a client component using props children. In this approach, the server component is not directly imported in the client component file, which would not be allowed. Instead, it is the parent (server) component that renders the ClientComponent structure by nesting inside the ServerComponent. This allows the ClientComponent to display the ServerComponent fragment in the appropriate place, maintaining compliance with RSC rules.

    Automatic code splitting

    React automatically separates server and client code. Server components are never sent to the browser, they remain exclusively on the server. In contrast, components marked ‘use client’ are bundled and sent to the client with full JavaScript code, ready for hydration.

    As a result, there is no need to manually configure code splitting, as in traditional SSR. The customer gets exactly what it needs – without unnecessary overhead, which significantly improves performance and application loading time.

    Integration with Next.js

    Next.js as of version 13.4+ has introduced support for React Server Components as part of its new “App Router” routing system. This allows developers to use server components in Next.js applications, enabling efficient management of server-side and client-side rendering.

    SSR vs RSC vs SSG

    In the React world, there are several approaches to server-side content rendering that are often confused with each other.

    • SSR (Server-Side Rendering) means that the server executes the component code and generates static HTML from it, which is sent to the client. However, for the application to be interactive, the client still needs to download and execute all the JavaScript code and perform hydration – that is, recreate the virtual DOM in the browser and assign events.
    • SSG (Static Site Generation) is a variation of SSR in which the rendering process takes place during the application build – HTML is generated once and then served as a static file. This solution is ideal for content that rarely changes.
    • RSC (React Server Components) goes one step further – rendering takes place on the server, but instead of sending the entire component code and executing it again on the client side, the server only sends HTML and the so-called Flight Payload – a serialized structure of React Elements. This means that server-side components do not have to be sent to the client as JavaScript code, which significantly reduces the bundle size and improves performance.

    This distinction makes it easier to understand when and why to use a particular approach, depending on the needs of the application.

    Flight Payload and component serialization

    The key mechanism that allows React Server Components to work without having to send JavaScript code to the client is Flight Payload – a special data format that contains a serialized component structure.

    When rendering a server-side component on the server, the result of the function (i.e. the React element structure) is converted to text – a JSON tree describing the element types, props and their hierarchy. This serialized tree is then sent to the browser as a data payload.

    On the client side, React reads this data and builds a Virtual DOM from it, without having to execute the server component code. This allows you to keep the full server logic outside the browser, while still giving React the ability to update and manage the UI structure.

    Flight Payload is the basis for React Server Components and what makes it possible to completely separate the server code from the client code while maintaining a consistent component model.

    Data transfer between components

    The server component can transfer data to the client component using props. Example:

    // ServerComponent.jsx
    import ClientCard from './ClientCard';
    
    async function ServerComponent() {
      const user = await getUser();
    
      return <ClientCard name={user.name} />;
    }
    
    export default ServerComponent;
    
    // ClientCard.jsx
    'use client';
    
    function ClientCard({ name }) {
      return <div>Welcome, {name}!</div>;
    }
    
    export default ClientCard;
    

    When should I not use React Server Components?

    • in simple SPA applications without SSR.
    • in static hosting environments (e.g. GitHub Pages).
    • in applications where each component requires dynamic interaction.

    Practical example: Let’s assume we want to create an app that displays a list of posts and allows users to add new ones. We can use a server-side component to retrieve and render the list of posts and a client-side component to handle user interactions:

    Server-side component:

    
    import db from 'imaginary-db';
    import AddPostButton from './AddPostButton';
    async function PostsList() {
      const posts = await db.query('SELECT * FROM posts');
      return (
        <div>
          {posts.map((post) => (
            <p key={post.id}>{post.title}</p>
          ))}
          <AddPostButton />
        </div>
      );
    }
    export default PostsList;
    

    Client component:

    'use client';
    import { useState } from 'react';
    function AddPostButton() {
      const [title, setTitle] = useState('');
     const handleAddPost = async () => {
        await fetch('/api/addPost', {
          method: 'POST',
          body: JSON.stringify({ title }),
        });
        setTitle('');
      };
    
      return (
        <div>
          <input
            type="text"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            placeholder="Nowy post"
          />
          <button onClick={handleAddPost}>Dodaj post</button>
        </div>
      );
    }
    
    export default AddPostButton;
    

    Benefits of using React Server Components

    • Performance: Reducing the amount of JavaScript code sent to the client leads to faster page loading and better application performance.
    • Security: Storing sensitive operations, such as database queries, on the server side increases application security.
    • code simplification: Direct access to server resources from React components simplifies application architecture, eliminating the need to create additional APIs.

    Limitations of React Server Components

    • cannot use hooks such as useState, useEffect.
    • cannot contain interactions – require client components.
    • They do not work outside the environment that supports RSC (e.g. older CRA versions).
    • It is not possible to dynamically import server components in the client code.

    Examples of using React Server Components

    React Server Components are particularly well suited to applications that make intensive use of data but do not require a large number of user interactions. Typical applications include:

    • Administrative dashboards with data retrieved from multiple sources,
    • CMS panels with dynamic content generation,
    • E-commerce shops where product data can be retrieved on the server,
    • Blogs and content portals where loading speed and SEO are crucial.

    Summary

    React Server Components introduce a new way of thinking about rendering in React applications, combining the advantages of server-side rendering with the flexibility of client-side components. They make it possible to create more efficient, secure, and maintainable applications. When used properly, RSCs can significantly improve the user experience and the developer’s work comfort.

    Connected articles
    See all
    Discover more topics