Skip to main content
Mohammad Shehadeh — home (MSH monogram, letter M filled with the Palestinian flag)

Understanding React Server Components

Published on
Last updated on
5 min read

React Server Components (RSC) are one of the biggest architectural shifts in React's history. They change how we think about component rendering, moving computation from the client to the server to build faster, more efficient applications.

What are React Server Components?

React Server Components are a new type of component that runs only on the server. Unlike traditional React components that run in the browser, Server Components run during the build or on the server at request time, sending only the rendered output to the client.

Core Attributes:

  • Execute on the server, not in the browser
  • Have direct access to backend resources (databases, file systems, APIs)
  • Cannot use browser-only features (useState, useEffect, event handlers)
  • Reduce JavaScript bundle size sent to the client

Framework Requirements

Important: You Need a Supporting Framework

React Server Components are a React feature, but you cannot use them in a regular React application (like Create React App). You need a framework that supports RSC, such as Next.js App Router, which handles the server-side rendering.

Currently Supported Frameworks:

  • Next.js App Router Most mature and widely adopted implementation
  • Remix Adding experimental support
  • Waku Minimalist React framework with RSC support

Next.js App Router is the Standard

Most RSC usage today happens through Next.js App Router. When you create a new Next.js app with App Router, Server Components are the default:

1npx create-next-app@latest my-app
2# Choose "Yes" for App Router during setup
3
4# All components in app/ directory are Server Components by default
5# Use 'use client' directive only when you need client-side features

How Server Components Work

When a user requests a page, Server Components run on the server, fetch the data they need, and send a serialized output to the client

1// ServerComponent.js (Server Component)
2import { db } from './database.js'; // Server-only import
3
4export default async function UserProfile({ userId }) {
5  // This runs on the server
6  const user = await db.users.findById(userId);
7  
8  return (
9      <div>
10          <h1>{user.name}</h1>
11          <p>{user.email}</p>
12          <p>Joined: {user.createdAt}</p>
13      </div>
14  );
15}

Server vs Client Components

Knowing the difference between Server and Client Components is key to using RSC well.

Server Components

Capabilities:

  • Server-side data fetching with direct database access
  • Access to server-only APIs
  • Zero impact on bundle size

Limitations:

  • No state (useState, useReducer)
  • No effects (useEffect, useLayoutEffect)
  • No event handlers (onClick, onChange)
  • No browser APIs (localStorage, window)
1// ✅ Server Component - Good practices
2async function BlogPost({ postId }) {
3  const post = await fetchPost(postId); // Direct server call
4  
5  return (
6      <article>
7          <h1>{post.title}</h1>
8          <p>{post.content}</p>
9          <PublishedDate date={post.publishedAt} />
10      </article>
11  );
12}

Client Components

Client Components are traditional React components that run in the browser. You mark them with the 'use client' directive.

Important Note on Hydration

Client Components still run on the server first during Server-Side Rendering (SSR), then hydrate on the client. This means any server-side data fetching in Client Components gets exposed during hydration, which is why components with the 'use client' directive cannot do sensitive operations. Server Components solve this by running only on the server and sending only the serialized output.

What is Hydration?

Hydration turns the static HTML generated by Server Components into an interactive React tree in the browser. It attaches event listeners to the DOM elements and makes them interactive.

1'use client';
2
3import { useState } from 'react';
4
5function InteractiveButton() {
6  const [count, setCount] = useState(0);
7  
8  return (
9      <button onClick={() => setCount(count + 1)}>
10          Clicked {count} times
11      </button>
12  );
13}

Practical Examples

Data Fetching Without Loading States

Server Components remove the need for loading states in many cases:

1// Traditional Client Component approach
2'use client';
3import { useState, useEffect } from 'react';
4
5function UserList() {
6  const [users, setUsers] = useState([]);
7  const [loading, setLoading] = useState(true);
8  
9  useEffect(() => {
10      fetchUsers().then(data => {
11          setUsers(data);
12          setLoading(false);
13      });
14  }, []);
15  
16  if (loading) return <div>Loading...</div>;
17  
18  return (
19      <ul>
20          {users.map(user => (
21              <li key={user.id}>{user.name}</li>
22          ))}
23      </ul>
24  );
25}
1// Server Component approach - No loading state needed!
2async function UserList() {
3  const users = await fetchUsers(); // Runs on server
4  
5  return (
6      <ul>
7          {users.map(user => (
8              <li key={user.id}>{user.name}</li>
9          ))}
10      </ul>
11  );
12}

Combining Server and Client Components

You can compose Server and Client Components together:

1// App.js (Server Component)
2import UserProfile from './UserProfile.js'; // Server Component
3import InteractivePanel from './InteractivePanel.js'; // Client Component
4
5export default async function App({ userId }) {
6  const user = await fetchUser(userId);
7  
8  return (
9      <div>
10          <UserProfile user={user} />
11          <InteractivePanel userId={userId} />
12      </div>
13  );
14}
1// InteractivePanel.js (Client Component)
2'use client';
3
4import { useState } from 'react';
5
6export default function InteractivePanel({ userId }) {
7  const [isExpanded, setIsExpanded] = useState(false);
8  
9  return (
10      <div>
11          <button onClick={() => setIsExpanded(!isExpanded)}>
12              {isExpanded ? 'Collapse' : 'Expand'} Details
13          </button>
14          {isExpanded && (
15              <div>Interactive content here...</div>
16          )}
17      </div>
18  );
19}

Streaming and Suspense

Server Components work well with React's Suspense for streaming:

1import { Suspense } from 'react';
2
3async function SlowComponent() {
4  await new Promise(resolve => setTimeout(resolve, 2000)); // Simulate slow operation
5  return <div>This loaded slowly!</div>;
6}
7
8export default function App() {
9  return (
10      <div>
11          <h1>My App</h1>
12          <Suspense fallback={<div>Loading slow component...</div>}>
13              <SlowComponent />
14          </Suspense>
15      </div>
16  );
17}

Benefits of React Server Components

Reduced Bundle Size

Server Components don't add to your JavaScript bundle, so pages load faster:

1// This large library only runs on the server
2import { MDXRemote } from 'next-mdx-remote-client/rsc'; // 100KB library
3
4export default async function RemoteMdxPage() {
5  // MDX text - can be from a database, CMS, fetch, anywhere...
6  const res = await fetch('https://...')
7  const markdown = await res.text()
8  return <MDXRemote source={markdown} />
9}
10// Client receives only the processed HTML, not the 100KB library!

Direct Backend Access

Access databases, file systems, and APIs directly, with no extra API layers:

1import { readFile } from 'fs/promises';
2import path from 'path';
3
4async function DocumentViewer({ filename }) {
5  // Direct file system access
6  const content = await readFile(
7      path.join(process.cwd(), 'documents', filename),
8      'utf-8'
9  );
10  
11  return <pre>{content}</pre>;
12}

Improved SEO and Performance

Because Server Components render on the server, they give you great SEO and faster initial page loads:

1async function ProductPage({ productId }) {
2  const product = await db.products.findById(productId);
3  
4  return (
5      <div>
6          <h1>{product.name}</h1>
7          <img src={product.imageUrl} alt={product.name} />
8          <p>{product.description}</p>
9      </div>
10  );
11}
12// This renders on the server, perfect for SEO!

When to Use Server Components

Ideal Use Cases

  • Data-heavy pages: Product listings, user profiles, dashboards
  • Static content: Blog posts, documentation, marketing pages
  • SEO-critical pages: Landing pages, product pages
  • Heavy computations: Image processing, data transformations

Avoid When You Need

  • User interactions: Forms, buttons, modals
  • Real-time updates: Chat applications, live data
  • Browser APIs: Geolocation, camera access
  • State management: Shopping carts, user preferences

Performance Considerations

Bundle Size Impact

1// Server Component - Zero bundle impact
2import { hugeLibrary } from 'path/to/huge-library'; // 500KB library
3
4export default async function HomePage() {
5  const processed = hugeLibrary.process(data); // Runs on server
6  return <div>{processed.result}</div>;
7}
8
9// vs Client Component - Adds to bundle
10'use client';
11import { hugeLibrary } from 'path/to/huge-library'; // 500KB added to client bundle
12
13export default function HomePage() {
14  const processed = hugeLibrary.process(data);
15  return <div>{processed.result}</div>;
16}

Streaming Benefits

Server Components let you render progressively:

1export default function Dashboard() {
2  return (
3      <div>
4          <QuickStats /> {/* Renders immediately */}
5
6          {/* Streams in when ready */}
7          <Suspense fallback={<Skeleton />}>
8              <SlowChart /> 
9          </Suspense>
10      </div>
11  );
12}

Quick Recap

  1. Architectural Shift: Server Components move computation from client to server, reducing JavaScript bundle sizes and improving performance.

  2. Direct Backend Access: Server Components can directly access databases, file systems, and APIs without additional API layers.

  3. Composition Model: Combine Server and Client Components by keeping interactive elements on the client and data fetching on the server.

  4. Performance Benefits: Faster initial page loads, better SEO, and reduced client-side JavaScript.

  5. Use Case Clarity: Use Server Components for data-heavy, static content and Client Components for interactive features.

  6. Framework Integration: Next.js App Router and other React frameworks are embracing Server Components as the default.

React Server Components aren't just a new feature, they're a paradigm shift that lets us build faster, more efficient web applications. Once you know when and how to use them, you can build better user experiences and simplify your application architecture.

References

Related Articles

GET IN TOUCH

Let's work together

I build fast, accessible, and delightful digital experiences for the web. Whether you have a project in mind or just want to connect, I'd love to hear from you.

Get in touch

or reach out directly at hello@mohammadshehadeh.com