Server Components
| Since: | React 19(2024) |
|---|
React Server Components are components that run only on the server. They are not sent to the client as a JavaScript bundle — only the rendered result (HTML or a serialized React tree) is delivered to the browser. Adopted by frameworks such as Next.js App Router, they bring benefits including smaller bundle sizes, direct access to databases and APIs, and protection of sensitive information.
Differences from Client Components
| Item | Server Components | Client Components |
|---|---|---|
| Execution environment | Server only. | Browser (also executed on the server during SSR). |
| JS bundle | Not included in the client bundle. | Sent to the client. |
| Interactivity | Event handlers, state, and hooks cannot be used. | Event handlers, state, and hooks can be used. |
| Data access | Can directly access databases, the file system, and server-only APIs. | Access only through API endpoints. |
| Declaration | Default (nothing written at the top of the file). | Write 'use client' at the top of the file. |
Basic Usage
// UserProfile.jsx (Server Component)
// Without 'use client' at the top,
// it is automatically treated as a Server Component
// A server-only data fetching function (e.g., DB access)
async function getUser(id) {
// This runs only on the server, so
// connection strings and API keys can be used directly
const res = await fetch('https://api.example.com/users/' + id, {
// In Next.js, the cache option controls caching behavior
cache: 'no-store',
});
return res.json();
}
// async/await can be used to fetch data directly
// Client Components require hooks (such as useEffect),
// but Server Components just need to be defined as async functions
export default async function UserProfile({ userId }) {
const user = await getUser(userId);
return (
<section>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
<p>Registered: {user.createdAt}</p>
</section>
);
}
Sample Code
An example of a Server Component fetching and displaying data while delegating only the interactive parts to a Client Component.
// app/articles/page.jsx (Server Component)
// This file runs only on the server
// It becomes a Server Component by not writing 'use client'
import LikeButton from './LikeButton'; // Import a Client Component
// Fetch the article list directly on the server
async function getArticles() {
const res = await fetch('https://api.example.com/articles', {
// Revalidate the cache every 60 seconds
next: { revalidate: 60 },
});
if (!res.ok) {
throw new Error('Failed to fetch articles');
}
return res.json();
}
export default async function ArticlesPage() {
// Receive data directly with await
// No useEffect + useState pattern needed
const articles = await getArticles();
return (
<main>
<h1>Articles</h1>
<ul>
{articles.map(function(article) {
return (
<li key={article.id}>
<h2>{article.title}</h2>
<p>{article.summary}</p>
{/* LikeButton requires a click event, so it is
separated as a Client Component */}
<LikeButton articleId={article.id} initialCount={article.likes} />
</li>
);
})}
</ul>
</main>
);
}
// app/articles/LikeButton.jsx (Client Component)
// Interactive operations (click, state) are needed, so
// 'use client' is declared at the top of the file
'use client';
import { useState } from 'react';
export default function LikeButton({ articleId, initialCount }) {
// state and event handlers are only available in Client Components
const [count, setCount] = useState(initialCount);
const [liked, setLiked] = useState(false);
function handleClick() {
if (liked) return;
// Increment the like count and update the UI immediately (optimistic update)
setCount(function(prev) { return prev + 1; });
setLiked(true);
// Record on the server via the API
fetch('/api/likes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ articleId: articleId }),
});
}
return (
<button onClick={handleClick} disabled={liked}>
{liked ? '♥ Liked' : '♡ Like'} ({count})
</button>
);
}
Usage Notes
Because Server Components run only on the server, browser APIs (window, document, localStorage, etc.) cannot be used. Hooks such as useState, useEffect, and useReducer are also unavailable. Parts that require interactivity or state management must be extracted as Client Components declared with 'use client'.
A Server Component can receive a Client Component as a child element. However, a Client Component cannot directly import a Server Component (passing it via children or props is possible). Also, props passed from a Server Component to a Client Component are limited to serializable values (strings, numbers, arrays, plain objects, etc.). Functions (event handlers) cannot be passed, which requires attention.
Combining with Suspense allows Server Components to be loaded asynchronously while displaying a fallback UI during data fetching. Unlike code splitting with lazy, this approach handles data fetching itself declaratively.
Common Mistakes
Using useState or useEffect inside a Server Component causes errors
Because Server Components run only on the server, browser-specific features (useState, useEffect, event handlers, etc.) cannot be used. To use these, add the 'use client' directive at the top of the file to convert it to a Client Component.
profile_ng.jsx
// Treated as a Server Component because there is no 'use client'
import { useState } from 'react';
// Using useState in a Server Component causes a build error or runtime error
async function PilotProfile({ id }) {
const [liked, setLiked] = useState(false);
const pilot = await fetchPilot(id);
return (
<div>
<h2>{pilot.name}</h2>
<button onClick={function() { setLiked(true); }}>
{liked ? 'Favorited' : 'Add to favorites'}
</button>
</div>
);
}
Fixed:
profile_ok.jsx
// Server Component (responsible for data fetching)
async function PilotProfile({ id }) {
const pilot = await fetchPilot(id);
return (
<div>
<h2>{pilot.name}</h2>
{/* Separate the interactive part into a Client Component */}
<LikeButton pilotId={id} />
</div>
);
}
like_button.jsx
'use client';
import { useState } from 'react';
// Client Component (responsible for interaction)
function LikeButton({ pilotId }) {
const [liked, setLiked] = useState(false);
return (
<button onClick={function() { setLiked(true); }}>
{liked ? 'Favorited' : 'Add to favorites'}
</button>
);
}
Passing a function as props from a Server Component to a Client Component causes a serialization error
Props passed from a Server Component to a Client Component must be JSON-serializable values. Functions, class instances, and objects with circular references cannot be passed. When a function needs to be used, define it inside the Client Component.
server_ng.jsx
// Server Component
async function ReportPage() {
// Functions cannot be passed as props to a Client Component
return <ReportButton onClick={function() { console.log('clicked'); }} />;
}
Fixed:
server_ok.jsx
'use client';
// Define the event handler inside the Client Component
function ReportButton() {
function handleClick() {
console.log('clicked');
}
return <button onClick={handleClick}>Submit report</button>;
}
If you find any errors or copyright issues, please contact us.