MSH Logo

Understanding React Server Components

Published on

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

What are React Server Components?

React Server Components are a new type of component that runs exclusively on the server. Unlike traditional React components that run in the browser, Server Components execute during the build process 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

How Server Components Work

When a user requests a page, Server Components execute on the server, retrieve the necessary data, 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

Understanding the distinction between Server and Client Components is crucial for effective RSC usage.

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. They're marked with the 'use client' directive.

Important Note on Hydration

Client Components still execute 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 perform sensitive operations. Server Components solve this by running exclusively on the server and sending only the serialized output.

What is Hydration?

Hydration is the process of converting the static HTML generated by Server Components into an interactive React tree in the browser. Basically, it's the process of attaching event listeners to the DOM elements and making 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 eliminate the need for loading states in many scenarios:

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 effectively:

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 beautifully 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

1. Reduced Bundle Size

Server Components don't add to your JavaScript bundle, leading to faster page loads:

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!

2. Direct Backend Access

Access databases, file systems, and APIs directly without additional 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}

3. Improved SEO and Performance

Since Server Components render on the server, they provide excellent 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 enable progressive rendering:

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 effectively 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 enables us to build faster, more efficient web applications. By understanding when and how to use them, you can create better user experiences while simplifying your application architecture.

References

Buy Me a Coffee at ko-fi.com
GET IN TOUCH

Let's work together

I build exceptional and accessible digital experiences for the web

WRITE AN EMAIL

or reach out directly at hello@mohammadshehadeh.com