Next.js and React Server Components (RSC) make your life easier

by Randy — 6 minutes

Server side rendering (SSR) becomes more mature and more common. React Server Components (RSC) are a way to render your components on the server. And is especially useful for API calls and data fetching without leaking any sensitive information to the client (like API keys, JWT, etc..). It's just an asynchronous React Component that is executed on the server.

Let's take a look at an example how we asynchronously fetch data from an API and render it on the server.

async function SimpleServerComponent() {  
  const data = await fetch('https://jsonplaceholder.typicode.com/todos/1').then( r -> r.json());
  return (<h1>{data.title}</h1>);
}

Because server components are executed on the server, they can be used to connect directly to the database (for example with prisma ORM) or fetch data from an API.

async function CurrentUserProfile() {    
  const currentUser = await getCurrentUser();    
  const user = await prisma.user.findUnique({        
     where: {            
       id: currentUser,
     },    
   });    

  return (<UserProfile user={user} />) 
}

In this example, we are fetching the current user from the database and rendering the user profile component with the fetched data. It demonstrates how server components can be used to fetch data from an API and render it on the server.

Using server components in Next.js

The React meta framework Next.js provides a way to use React Server Components in your application. Below is a simple example of how to fetch data from an API and render it on the server. Even the Page component could be a server component.

export default async function Page({ params: { id } } : { params: Promise<{ id: string }> }) {    
  const id = await params.id;
  const data = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`).then(result => result.json()); 
  
  return (<div>{data.title}</div>);
}

How to handle slow server components?

Server components can be slow, if it does some heavy processing such as fetching data from an API or database or any other slow operation. In such situations, it's blocking the server to complete the render on the server. Instead, you can use <Suspense> to show a loading state while the server component is being executed.

The server will continue to render the rest of the page and sends the HTML response to the client. Once the server component is resolved, the client will receive the rest of the HTML. For more information, check out the Nextjs documentation on streaming.

Here's an example of how to use Suspense to show a loading state while the server component is being executed.

import { Suspense } from 'react';
async function ServerComponentTodoOverview() {
  const todos = await fetch('https://jsonplaceholder.typicode.com/todos').then((res) => res.json());
  
  return (        
     <ul>{todos.map((todo) => (<li key={todo.id}>{todo.title}</li>))}</ul>    
  );
}

export default async function Page() {
  return (
    <div>
       <h1>Todos</h1>
       <Suspense fallback={<div>Loading todos UI</div>}>
           <ServerComponent />
       </Suspense>        
     </div>);
}

Fetching data with API key and secrets on the server

API keys and other secrets should never be exposed to the client, because of all the security risks. Now, you can use server components to handle API requests without exposing any secrets to the client.

With complete control over what to send to the client and how to handle the response, so your application becomes more secure. For more information about how to make use code run only on the server read this: https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#keeping-server-only-code-out-of-the-client-environment

Server-only to prevent leading server-side code to run on the client

When you import server-only, you will receive a build-time error explaining that this module can only be used on the server. This prevents leaking of sensitive data such as API keys. You will need to install the server-only package https://www.npmjs.com/package/server-only to use this feature.

Example how to use server-only

See the example below with a Github API call using a client ID and client secret on the server to fetch the current user. This is an example of how to use server components to handle API requests without exposing any secrets to the client.

import 'server-only';

async function getCurrentUser() {     
  const clientId = process.env.CLIENT_ID;     
  const clientSecret = process.env.CLIENT_SECRET;     

  const response = await fetch(`https://api.github.com/user`, {         
    headers: {             
        Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`, 
     },     
   }); 

  return await response.json();
}

async function CurrentUserProfile() {    
  const currentUser = await getCurrentUser();    

  return (<UserProfile user={user} />) 
}

Server components and user interaction

React Server Components are only executed on the server. This means you can't write any code that interacts with the user or needs to be hydrated on the client. For example, you can't use a hook in a server component. You need to use client components to handle user interactions.

To create a client component you only need te directive use client. See here below of how to use a client component with interaction in a React server component.

The MyClientComponentWithInteraction component is a client component that can be used to display a modal. The modal can be opened and closed by the user and the content of the modal is passed as children, so the fetched data can be displayed in the modal.

'use client';

// my-client-component-with-interaction.tsx
function MyClientComponentWithInteraction( { children, title, open = false }: PropsWithChildren<{title: string, open: boolean}>) {
    const [isOpen, setIsOpen] = useState(open);

    if(!isOpen) return null;    

   return (        
      <div className="modal">
         <h2>{title}</h2>
         {children}            
         <button onClick={() => setIsOpen(false)}>Close</button>
     </div>    
  );
}

// my-server-component-with-client-component.tsx
async function MyServerComponentWithClientComponent() {
    const data = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((res) => res.json());

    return (        
       <MyClientComponentWithInteraction title="My Todo modal">
          <h3>{data.title}</h3>
       </MyClientComponentWithInteraction>    
    );
}

To learn more about client components and how to use them see: https://nextjs.org/docs/app/building-your-application/rendering/client-components.

Simplifying the process of handling asynchronous functions on the server

In conclusion, Next.js and React Server Components simplify the process of handling asynchronous functions on the server. And with Suspense, the client doesn't have to wait for the server to resolve all components to complete the render. This is a great way to improve the user experience. The downside is that it can become complicated if you don't know how to use server components correctly in combination with client components.

I created a repository with a Next.js example how to use server components on https://github.com/rkonings/blog-nextjs15-server-components

This article uses Next.js and React Server Components to demonstrate how to handle asynchronous functions on the server. But since React 19, server components are also available without Next.js. You can check out the official documentation for more information on server components on the React documentation

meerdivotion

Cases

Blogs

Event