React Server Components Explained: The Future of React
Understand React Server Components, how they work, and why they're revolutionizing the way we build React applications
React Server Components Explained: The Future of React
React Server Components (RSC) represent a paradigm shift in how we build React applications. They enable server-side rendering of components without sending JavaScript to the client, dramatically improving performance and user experience.
What Are React Server Components?
React Server Components are components that render exclusively on the server. Unlike traditional Server-Side Rendering (SSR), RSCs don't send their JavaScript code to the client at all.
Key Differences from Traditional SSR
<span class="hljs-comment">// Traditional SSR: Renders on server, hydrates on client</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">UserProfile</span>(<span class="hljs-params">{ userId }</span>) {
<span class="hljs-keyword">const</span> [user, setUser] = <span class="hljs-title function_">useState</span>(<span class="hljs-literal">null</span>);
<span class="hljs-title function_">useEffect</span>(<span class="hljs-function">() =></span> {
<span class="hljs-title function_">fetchUser</span>(userId).<span class="hljs-title function_">then</span>(setUser);
}, [userId]);
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>{user?.name}<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
}
<span class="hljs-comment">// Server Component: Renders only on server</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">UserProfile</span>(<span class="hljs-params">{ userId }</span>) {
<span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchUser</span>(userId);
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>{user.name}<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
}
Traditional SSR:
- Server renders HTML
- Client receives HTML + JavaScript
- Client hydrates (attaches event handlers)
- Full component code ships to client
React Server Components:
- Server renders component
- Client receives serialized output (not HTML or JS)
- No hydration needed for server components
- Zero JavaScript sent for server components
Benefits of Server Components
1. Zero-Bundle-Size Components
Server Components don't add to your JavaScript bundle. Heavy dependencies stay on the server.
<span class="hljs-comment">// Server Component - markdown-it doesn't ship to client</span>
<span class="hljs-keyword">import</span> markdownIt <span class="hljs-keyword">from</span> <span class="hljs-string">'markdown-it'</span>; <span class="hljs-comment">// Heavy library</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">BlogPost</span>(<span class="hljs-params">{ slug }</span>) {
<span class="hljs-keyword">const</span> post = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchPost</span>(slug);
<span class="hljs-keyword">const</span> md = <span class="hljs-title function_">markdownIt</span>();
<span class="hljs-keyword">const</span> html = md.<span class="hljs-title function_">render</span>(post.<span class="hljs-property">content</span>);
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">dangerouslySetInnerHTML</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">__html:</span> <span class="hljs-attr">html</span> }} /></span></span>;
}
2. Direct Backend Access
Server Components can access databases, filesystems, and APIs directly without creating API routes.
<span class="hljs-comment">// Server Component - direct database access</span>
<span class="hljs-keyword">import</span> { db } <span class="hljs-keyword">from</span> <span class="hljs-string">'./database'</span>;
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">UserList</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> db.<span class="hljs-property">users</span>.<span class="hljs-title function_">findMany</span>({
<span class="hljs-attr">orderBy</span>: { <span class="hljs-attr">createdAt</span>: <span class="hljs-string">'desc'</span> },
<span class="hljs-attr">take</span>: <span class="hljs-number">10</span>
});
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">ul</span>></span>
{users.map(user => (
<span class="hljs-tag"><<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{user.id}</span>></span>{user.name}<span class="hljs-tag"></<span class="hljs-name">li</span>></span>
))}
<span class="hljs-tag"></<span class="hljs-name">ul</span>></span></span>
);
}
3. Automatic Code Splitting
React automatically code-splits between Server and Client Components.
<span class="hljs-comment">// Server Component</span>
<span class="hljs-keyword">import</span> <span class="hljs-title class_">ClientButton</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'./ClientButton'</span>; <span class="hljs-comment">// Only this gets sent to client</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">ProductPage</span>(<span class="hljs-params">{ id }</span>) {
<span class="hljs-keyword">const</span> product = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchProduct</span>(id);
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">h1</span>></span>{product.name}<span class="hljs-tag"></<span class="hljs-name">h1</span>></span>
<span class="hljs-tag"><<span class="hljs-name">p</span>></span>{product.description}<span class="hljs-tag"></<span class="hljs-name">p</span>></span>
<span class="hljs-tag"><<span class="hljs-name">ClientButton</span> <span class="hljs-attr">productId</span>=<span class="hljs-string">{id}</span> /></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
);
}
4. Improved Performance
By rendering on the server and sending less JavaScript, pages load faster.
Server vs Client Components
Server Components (Default)
Can:
- Fetch data directly
- Access backend resources
- Keep sensitive information secure
- Use large dependencies
- Reduce client bundle size
Cannot:
- Use React hooks (useState, useEffect, etc.)
- Use browser APIs
- Handle user interactions directly
- Maintain state
<span class="hljs-comment">// Server Component (default in Next.js 13+ app directory)</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">BlogList</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> posts = <span class="hljs-keyword">await</span> <span class="hljs-title function_">getPosts</span>();
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>
{posts.map(post => (
<span class="hljs-tag"><<span class="hljs-name">article</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{post.id}</span>></span>
<span class="hljs-tag"><<span class="hljs-name">h2</span>></span>{post.title}<span class="hljs-tag"></<span class="hljs-name">h2</span>></span>
<span class="hljs-tag"><<span class="hljs-name">p</span>></span>{post.excerpt}<span class="hljs-tag"></<span class="hljs-name">p</span>></span>
<span class="hljs-tag"></<span class="hljs-name">article</span>></span>
))}
<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
);
}
Client Components
Can:
- Use React hooks
- Handle user interactions
- Use browser APIs
- Maintain state and lifecycle
Cannot:
- Be async functions
- Access backend directly (need API routes)
<span class="hljs-string">'use client'</span>; <span class="hljs-comment">// Explicitly mark as Client Component</span>
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">function</span> <span class="hljs-title function_">SearchBar</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> [query, setQuery] = <span class="hljs-title function_">useState</span>(<span class="hljs-string">''</span>);
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">input</span>
<span class="hljs-attr">value</span>=<span class="hljs-string">{query}</span>
<span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =></span> setQuery(e.target.value)}
placeholder="Search..."
/></span>
);
}
Composition Patterns
Pattern 1: Server Component with Client Component Children
<span class="hljs-comment">// ServerWrapper.js - Server Component</span>
<span class="hljs-keyword">import</span> <span class="hljs-title class_">ClientCounter</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'./ClientCounter'</span>;
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">ServerWrapper</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> initialCount = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchInitialCount</span>();
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">h1</span>></span>Server-rendered content<span class="hljs-tag"></<span class="hljs-name">h1</span>></span>
<span class="hljs-tag"><<span class="hljs-name">ClientCounter</span> <span class="hljs-attr">initialCount</span>=<span class="hljs-string">{initialCount}</span> /></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
);
}
<span class="hljs-comment">// ClientCounter.js - Client Component</span>
<span class="hljs-string">'use client'</span>;
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">ClientCounter</span>(<span class="hljs-params">{ initialCount }</span>) {
<span class="hljs-keyword">const</span> [count, setCount] = <span class="hljs-title function_">useState</span>(initialCount);
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =></span> setCount(count + 1)}>
Count: {count}
<span class="hljs-tag"></<span class="hljs-name">button</span>></span></span>
);
}
Pattern 2: Passing Server Components as Props
You can pass Server Components to Client Components as children or props.
<span class="hljs-comment">// Layout.js - Client Component</span>
<span class="hljs-string">'use client'</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Layout</span>(<span class="hljs-params">{ children }</span>) {
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"layout"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">nav</span>></span>{/* client-side navigation */}<span class="hljs-tag"></<span class="hljs-name">nav</span>></span>
<span class="hljs-tag"><<span class="hljs-name">main</span>></span>{children}<span class="hljs-tag"></<span class="hljs-name">main</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
);
}
<span class="hljs-comment">// Page.js - Server Component</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Page</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchData</span>();
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">Layout</span>></span>
<span class="hljs-tag"><<span class="hljs-name">ServerContent</span> <span class="hljs-attr">data</span>=<span class="hljs-string">{data}</span> /></span>
<span class="hljs-tag"></<span class="hljs-name">Layout</span>></span></span>
);
}
Pattern 3: Context Providers
Context Providers must be Client Components, but can wrap Server Components.
<span class="hljs-comment">// ThemeProvider.js - Client Component</span>
<span class="hljs-string">'use client'</span>;
<span class="hljs-keyword">import</span> { createContext, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-title class_">ThemeContext</span> = <span class="hljs-title function_">createContext</span>();
<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">ThemeProvider</span>(<span class="hljs-params">{ children }</span>) {
<span class="hljs-keyword">const</span> [theme, setTheme] = <span class="hljs-title function_">useState</span>(<span class="hljs-string">'light'</span>);
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">ThemeContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">theme</span>, <span class="hljs-attr">setTheme</span> }}></span>
{children}
<span class="hljs-tag"></<span class="hljs-name">ThemeContext.Provider</span>></span></span>
);
}
<span class="hljs-comment">// Layout.js - Server Component</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">ThemeProvider</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'./ThemeProvider'</span>;
<span class="hljs-keyword">function</span> <span class="hljs-title function_">RootLayout</span>(<span class="hljs-params">{ children }</span>) {
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">html</span>></span>
<span class="hljs-tag"><<span class="hljs-name">body</span>></span>
<span class="hljs-tag"><<span class="hljs-name">ThemeProvider</span>></span>
{children}
<span class="hljs-tag"></<span class="hljs-name">ThemeProvider</span>></span>
<span class="hljs-tag"></<span class="hljs-name">body</span>></span>
<span class="hljs-tag"></<span class="hljs-name">html</span>></span></span>
);
}
Data Fetching Patterns
Sequential Fetching (Waterfall)
<span class="hljs-comment">// ⚠️ Sequential - slower</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Page</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchUser</span>();
<span class="hljs-keyword">const</span> posts = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchPosts</span>(user.<span class="hljs-property">id</span>);
<span class="hljs-keyword">const</span> comments = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchComments</span>(posts[<span class="hljs-number">0</span>].<span class="hljs-property">id</span>);
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>{/* render */}<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
}
Parallel Fetching
<span class="hljs-comment">// ✅ Parallel - faster</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Page</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> [user, posts, comments] = <span class="hljs-keyword">await</span> <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">all</span>([
<span class="hljs-title function_">fetchUser</span>(),
<span class="hljs-title function_">fetchPosts</span>(),
<span class="hljs-title function_">fetchComments</span>()
]);
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>{/* render */}<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
}
Streaming with Suspense
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Suspense</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">function</span> <span class="hljs-title function_">Page</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">h1</span>></span>My Page<span class="hljs-tag"></<span class="hljs-name">h1</span>></span>
<span class="hljs-tag"><<span class="hljs-name">Suspense</span> <span class="hljs-attr">fallback</span>=<span class="hljs-string">{</span><<span class="hljs-attr">LoadingSpinner</span> /></span>}>
<span class="hljs-tag"><<span class="hljs-name">ServerComponent</span> /></span>
<span class="hljs-tag"></<span class="hljs-name">Suspense</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
);
}
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">ServerComponent</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchSlowData</span>();
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>{data}<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
}
Best Practices
1. Keep Client Components Small
Move interactivity as deep as possible in your component tree.
<span class="hljs-comment">// ❌ Bad: Entire page is client component</span>
<span class="hljs-string">'use client'</span>;
<span class="hljs-keyword">function</span> <span class="hljs-title function_">ProductPage</span>(<span class="hljs-params">{ productId }</span>) {
<span class="hljs-keyword">const</span> [quantity, setQuantity] = <span class="hljs-title function_">useState</span>(<span class="hljs-number">1</span>);
<span class="hljs-keyword">const</span> product = <span class="hljs-title function_">useProduct</span>(productId);
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">h1</span>></span>{product.name}<span class="hljs-tag"></<span class="hljs-name">h1</span>></span>
<span class="hljs-tag"><<span class="hljs-name">p</span>></span>{product.description}<span class="hljs-tag"></<span class="hljs-name">p</span>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =></span> setQuantity(q => q + 1)}>
Add to cart: {quantity}
<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
);
}
<span class="hljs-comment">// ✅ Good: Only interactive part is client component</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">ProductPage</span>(<span class="hljs-params">{ productId }</span>) {
<span class="hljs-keyword">const</span> product = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchProduct</span>(productId);
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">h1</span>></span>{product.name}<span class="hljs-tag"></<span class="hljs-name">h1</span>></span>
<span class="hljs-tag"><<span class="hljs-name">p</span>></span>{product.description}<span class="hljs-tag"></<span class="hljs-name">p</span>></span>
<span class="hljs-tag"><<span class="hljs-name">AddToCartButton</span> /></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
);
}
<span class="hljs-comment">// Small client component</span>
<span class="hljs-string">'use client'</span>;
<span class="hljs-keyword">function</span> <span class="hljs-title function_">AddToCartButton</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> [quantity, setQuantity] = <span class="hljs-title function_">useState</span>(<span class="hljs-number">1</span>);
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =></span> setQuantity(q => q + 1)}>
Add to cart: {quantity}
<span class="hljs-tag"></<span class="hljs-name">button</span>></span></span>
);
}
2. Use Server Components for Data Fetching
Fetch data in Server Components to avoid client-side waterfalls.
<span class="hljs-comment">// ✅ Good: Fetch in server component</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">UserDashboard</span>(<span class="hljs-params">{ userId }</span>) {
<span class="hljs-keyword">const</span> userData = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchUser</span>(userId);
<span class="hljs-keyword">const</span> analytics = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchAnalytics</span>(userId);
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">UserProfile</span> <span class="hljs-attr">data</span>=<span class="hljs-string">{userData}</span> /></span>
<span class="hljs-tag"><<span class="hljs-name">AnalyticsChart</span> <span class="hljs-attr">data</span>=<span class="hljs-string">{analytics}</span> /></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
);
}
3. Serialize Props Correctly
Only serializable data can pass from Server to Client Components.
<span class="hljs-comment">// ❌ Bad: Cannot pass functions</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">ServerParent</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> <span class="hljs-title function_">handleClick</span> = (<span class="hljs-params"></span>) => <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'clicked'</span>);
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">ClientChild</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleClick}</span> /></span></span>; <span class="hljs-comment">// Error!</span>
}
<span class="hljs-comment">// ✅ Good: Define function in client component</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">ServerParent</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">ClientChild</span> /></span></span>;
}
<span class="hljs-comment">// ClientChild.js</span>
<span class="hljs-string">'use client'</span>;
<span class="hljs-keyword">function</span> <span class="hljs-title function_">ClientChild</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> <span class="hljs-title function_">handleClick</span> = (<span class="hljs-params"></span>) => <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'clicked'</span>);
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleClick}</span>></span>Click<span class="hljs-tag"></<span class="hljs-name">button</span>></span></span>;
}
4. Cache Data Fetches
Use React's cache function to deduplicate requests.
<span class="hljs-keyword">import</span> { cache } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">const</span> getUser = <span class="hljs-title function_">cache</span>(<span class="hljs-title function_">async</span> (id) => {
<span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-string">`/api/users/<span class="hljs-subst">${id}</span>`</span>);
<span class="hljs-keyword">return</span> response.<span class="hljs-title function_">json</span>();
});
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">UserProfile</span>(<span class="hljs-params">{ userId }</span>) {
<span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> <span class="hljs-title function_">getUser</span>(userId);
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>{user.name}<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
}
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">UserPosts</span>(<span class="hljs-params">{ userId }</span>) {
<span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> <span class="hljs-title function_">getUser</span>(userId); <span class="hljs-comment">// Same request, cached!</span>
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>Posts by {user.name}<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
}
Common Pitfalls
1. Importing Client Components into Server Components
<span class="hljs-comment">// ❌ Bad: Can accidentally make server component a client component</span>
<span class="hljs-keyword">import</span> <span class="hljs-title class_">ClientComponent</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'./ClientComponent'</span>; <span class="hljs-comment">// Has 'use client'</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">ServerComponent</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">ClientComponent</span> /></span></span>;
}
2. Using Hooks in Server Components
<span class="hljs-comment">// ❌ Bad: Cannot use hooks in server components</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">ServerComponent</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> [state, setState] = <span class="hljs-title function_">useState</span>(<span class="hljs-number">0</span>); <span class="hljs-comment">// Error!</span>
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>{state}<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
}
3. Trying to Pass Non-Serializable Props
<span class="hljs-comment">// ❌ Bad: Cannot pass Date objects, functions, etc.</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">ServerComponent</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">ClientComponent</span> <span class="hljs-attr">date</span>=<span class="hljs-string">{new</span> <span class="hljs-attr">Date</span>()} /></span></span>; <span class="hljs-comment">// Error!</span>
}
<span class="hljs-comment">// ✅ Good: Pass serializable data</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">ServerComponent</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">ClientComponent</span> <span class="hljs-attr">date</span>=<span class="hljs-string">{new</span> <span class="hljs-attr">Date</span>()<span class="hljs-attr">.toISOString</span>()} /></span></span>;
}
Using with Next.js App Router
Next.js 13+ App Router fully supports Server Components:
<span class="hljs-comment">// app/page.js - Server Component by default</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">HomePage</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> posts = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchPosts</span>();
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">main</span>></span>
<span class="hljs-tag"><<span class="hljs-name">h1</span>></span>My Blog<span class="hljs-tag"></<span class="hljs-name">h1</span>></span>
{posts.map(post => (
<span class="hljs-tag"><<span class="hljs-name">article</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{post.id}</span>></span>
<span class="hljs-tag"><<span class="hljs-name">h2</span>></span>{post.title}<span class="hljs-tag"></<span class="hljs-name">h2</span>></span>
<span class="hljs-tag"><<span class="hljs-name">p</span>></span>{post.excerpt}<span class="hljs-tag"></<span class="hljs-name">p</span>></span>
<span class="hljs-tag"></<span class="hljs-name">article</span>></span>
))}
<span class="hljs-tag"></<span class="hljs-name">main</span>></span></span>
);
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">HomePage</span>;
Loading States
<span class="hljs-comment">// app/loading.js</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Loading</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>Loading...<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
}
<span class="hljs-comment">// app/page.js</span>
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Page</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchData</span>(); <span class="hljs-comment">// Shows loading.js while fetching</span>
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>{data}<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
}
Error Handling
<span class="hljs-comment">// app/error.js</span>
<span class="hljs-string">'use client'</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Error</span>(<span class="hljs-params">{ error, reset }</span>) {
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">h2</span>></span>Something went wrong!<span class="hljs-tag"></<span class="hljs-name">h2</span>></span>
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =></span> reset()}>Try again<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
);
}
Conclusion
React Server Components represent a fundamental shift in React architecture:
- Better Performance: Less JavaScript, faster load times
- Simplified Data Fetching: Direct backend access in components
- Automatic Optimization: Code splitting and bundling handled for you
- Flexible Composition: Mix server and client components as needed
The learning curve is worth it. Start by making most components Server Components by default, and only add 'use client' when you need interactivity. This approach will lead to faster, more efficient React applications.
The future of React is hybrid: server and client working together seamlessly.
Jordan Patel
Web Developer & Technology Enthusiast