Next.js Server Components (RSC) are a new way to build modern web apps that are faster, more efficient, and more secure. They help reduce JavaScript bundle sizes and improve initial load times.
In this article, youβll learn:
- What Server Components are
- When to use Server vs. Client Components
- The difference between Server Components and Server-Side Rendering (SSR)
- How Server Components work with CDNs
- How Server Functions and Actions improve data handling
π¦ What Are Server Components?
Server Components are React components that only run on the server. They never get shipped to the browser.
What does that mean?
- The browser never downloads the componentβs code.
- Only the rendered HTML or data is sent to the browser.
- This makes the app faster and more secure.
β Benefits of Server Components
Problems with Client-Side Rendering (CSR)
In CSR:
- JavaScript bundles get big.
- The browser must download, parse, and execute a lot of JavaScript.
- The initial display (FCP: First Contentful Paint) is often slow.
Why Server Components Are Better
- β Smaller JavaScript Bundles: Server Components are not included in the client bundle.
- β Faster FCP: Less JavaScript = faster initial loading.
- β Progressive Rendering: Content can start showing while other parts are still loading.
- β Secure Data Fetching: You can safely use API keys and database calls on the server.
- β CDN Caching: The rendered output can be stored on a CDN for super-fast delivery.
π Server Components and CDNs
One of the biggest advantages of Server Components is that their rendered results can be cached in a CDN.
How It Works:
- Server Components run on the server and generate HTML or RSC Payload (Reactβs special format).
- This output can be uploaded to a CDN (Content Delivery Network).
- The CDN delivers the content from the nearest edge location to the user.
What Does the Browser Receive?
- The browser only gets the final HTML or RSC Payload.
- The original Server Component logic never gets sent to the client.
Why This Is Great:
- β‘ Fast load speeds like static sites.
- π Secure: API keys and secrets stay on the server.
- π¦ Lightweight bundles since no server logic is included.
π₯οΈ Server Components vs. Client Components
Feature | Server Components | Client Components |
---|---|---|
Run Location | Server | Browser |
JavaScript Bundle | Not included | Included |
Use Cases | Data fetching, secure API calls | UI interactions, state, browser APIs |
Example | Render markdown, fetch database | Handle clicks, form input |
Use Client Components when you need:
- State management (e.g.
useState
,onClick
). - Lifecycle hooks (e.g.
useEffect
). - Browser APIs (e.g.
window
,localStorage
).
Use Server Components when you need:
- Database/API fetching.
- Secure data handling (API keys, tokens).
- Smaller bundles and faster initial paint (FCP).
π οΈ Server Components vs. Server-Side Rendering (SSR)
Feature | Server Components | Server-Side Rendering (SSR) |
---|---|---|
Rendering | Server, streamed as RSC Payload | Server, full HTML |
JavaScript | Only Client Components sent | Full JavaScript sent |
Initial HTML | Fast preview + placeholders | Fully rendered |
Goal | Minimize JS, improve load speed | Fully hydrate the page |
Key Difference:
Server Components focus on reducing JavaScript bundles and improving streaming. SSR focuses on delivering full HTML and hydrating everything.
π Example: Markdown Rendering with Server Components
import marked from 'marked'; // Not sent to browser
import sanitizeHtml from 'sanitize-html'; // Not sent to browser
async function Page({page}) {
const content = await file.readFile(`${page}.md`);
return <div>{sanitizeHtml(marked(content))}</div>;
}
βοΈ Expensive libraries like marked and sanitize-html stay on the server.
βοΈ The client only receives the final HTML.
π‘ Example: Fetching Data with Server Components
import db from './database';
async function Note({id}) {
const note = await db.notes.get(id);
return (
<div>
<Author id={note.authorId} />
<p>{note.content}</p>
</div>
);
}
async function Author({id}) {
const author = await db.authors.get(id);
return <span>By: {author.name}</span>;
}
βοΈ Data is fetched directly on the server.
βοΈ The browser only sees the rendered result.
β³ Async Server Components and Streaming
Server Components support async/await directly.
// Server Component
async function Page({id}) {
const note = await db.notes.get(id);
const commentsPromise = db.comments.get(note.id);
return (
<div>
{note.content}
<Suspense fallback={<p>Loading Comments...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
</div>
);
}
// Client Component
"use client";
import {use} from 'react';
function Comments({commentsPromise}) {
const comments = use(commentsPromise);
return comments.map(comment => <p>{comment}</p>);
}
βοΈ High-priority data (like note) is fetched first.
βοΈ Lower-priority data (like comments) can load later, improving perceived speed.
βοΈ Server Functions and Actions
Server Functions let Client Components call functions that run on the server.
// Server Component
import Button from './Button';
function EmptyNote() {
async function createNoteAction() {
"use server";
await db.notes.create();
}
return <Button onClick={createNoteAction} />;
}
Example: Client Actions
Β₯
"use server";
export async function updateName(name) {
if (!name) return {error: 'Name is required'};
await db.users.updateName(name);
}
"use client";
import {updateName} from './actions';
function UpdateName() {
const [name, setName] = useState('');
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const submitAction = async () => {
startTransition(async () => {
const {error} = await updateName(name);
if (error) setError(error);
else setName('');
});
};
return (
<form action={submitAction}>
<input type="text" name="name" disabled={isPending} />
{error && <span>Failed: {error}</span>}
</form>
);
}
βοΈ Server logic stays on the server.
βοΈ The client can safely trigger these functions without exposing secrets.
β‘ useActionState for Easier Server Calls
You can simplify Server Function calls using useActionState.
"use client";
import {updateName} from './actions';
function UpdateName() {
const [state, submitAction, isPending] = useActionState(updateName, {error: null});
return (
<form action={submitAction}>
<input type="text" name="name" disabled={isPending}/>
{state.error && <span>Failed: {state.error}</span>}
</form>
);
}
βοΈ Cleaner syntax
βοΈ Built-in loading and response management
summary
Feature | Purpose |
---|---|
Server Components | Run on the server, reduce JS, secure data, CDN caching |
Client Components | Handle UI, state, and browser APIs |
Server Functions | Server-side logic triggered from the client |
Actions | Safely manage server calls and loading states |
π References
https://react.dev/reference/rsc/server-functions
Top comments (0)