You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
One of Next.js's most powerful features is React Server Components (RSC). Components run on the server by default, reducing the JavaScript sent to the browser and enabling direct data access. This lesson covers server vs client components, the "use client" directive, server actions, fetching data in server components, caching, and revalidation.
| Feature | Server Component (default) | Client Component ("use client") |
|---|---|---|
| Runs on | Server only | Server (SSR) + Browser |
| JavaScript sent to browser | None | Yes — included in the bundle |
| Can access DB / file system | Yes | No |
Can use hooks (useState, etc.) | No | Yes |
| Can use event handlers | No | Yes |
| Can render server components | Yes | No (can accept as children) |
Use Server Components for: Use Client Components for:
├── Fetching data ├── Interactivity (onClick, onChange)
├── Accessing backend resources ├── useState, useEffect, useReducer
├── Rendering static/dynamic content ├── Browser APIs (localStorage, etc.)
└── Keeping secrets on the server └── Third-party client libraries
By default, every component in the App Router is a server component. To make it a client component, add "use client" at the top of the file:
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
"use client" creates a boundary. Everything imported by a client component is also treated as client code:
ServerComponent.tsx (server)
└── imports ClientComponent.tsx ("use client" — client boundary)
└── imports HelperComponent.tsx (also becomes client)
Tip: Keep the client boundary as low in the tree as possible. Only mark the interactive leaf components as client components.
Server components can fetch data directly — no useEffect or useState needed:
// src/app/posts/page.tsx — Server Component
interface Post {
id: number;
title: string;
body: string;
}
export default async function PostsPage() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
const posts: Post[] = await res.json();
return (
<main>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
))}
</ul>
</main>
);
}
import { prisma } from "@/lib/prisma";
export default async function UsersPage() {
const users = await prisma.user.findMany();
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name} — {user.email}</li>
))}
</ul>
);
}
Next.js automatically caches fetch requests in server components.
// Cached indefinitely (default — static data)
const res = await fetch("https://api.example.com/data");
// Revalidate every 60 seconds (ISR)
const res = await fetch("https://api.example.com/data", {
next: { revalidate: 60 },
});
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.