How to Use React Server Components
React Server Components let you render parts of your UI on the server before they reach the browser. This guide shows you how to use them in a Next.js App Router project, where Server Components are supported by default.
What you’ll build or solve
You’ll create a Server Component that fetches data and renders HTML without sending extra component JavaScript to the browser. Done means you can keep server-only code on the server and move interactive UI into Client Components when needed.
Learn React on Mimo
When this approach works best
React Server Components work best when:
- You need to fetch data before rendering a page.
- You want to keep database, API, or file-system logic out of the browser.
- You want to reduce client-side JavaScript.
- You are building a page with mostly static or read-only content.
This is a bad idea for components that need browser APIs, click handlers, local state, or effects. Use a Client Component for those parts.
Prerequisites
- A React framework that supports Server Components, such as Next.js App Router
- Basic React component knowledge
- Basic knowledge of async functions and
fetch - A running project with an
appfolder
React Server Components are usually used through a framework. In Next.js App Router, files like app/page.js and app/layout.js are Server Components by default.
Step-by-step instructions
Step 1: Create a Server Component
In a Next.js App Router project, create or open a page file:
Bash
app/page.js
Add a normal React component:
JavaScript
export default function HomePage() {
return (
<main>
<h1>Welcome back</h1>
<p>This page is rendered as a Server Component.</p>
</main>
);
}
You do not need to add a special directive to make this a Server Component. In the App Router, it is server-rendered by default.
Do not add "use client" at the top unless the component needs client-side features.
What to look for
- No
"use client"directive at the top. - No
useState,useEffect, or browser-only APIs. - The component can be an
asyncfunction if it needs data. - Server-only logic should stay in this component or in server-only helper files.
Step 2: Fetch data on the server
Server Components can fetch data before rendering.
JavaScript
async function getPosts() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=3");
if (!response.ok) {
throw new Error("Failed to fetch posts");
}
return response.json();
}
export default async function HomePage() {
const posts = await getPosts();
return (
<main>
<h1>Latest posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
);
}
The data is loaded on the server, then the rendered result is sent to the browser.
This keeps the fetching logic out of the client bundle. It also avoids the common pattern of rendering an empty page first and then fetching data in useEffect.
Step 3: Move interactive code into a Client Component
Server Components cannot use local state, effects, event handlers, or browser APIs. If you need interactivity, create a separate Client Component.
Create a file:
Bash
app/components/LikeButton.js
Add "use client" at the top:
JavaScript
"use client";
import { useState } from "react";
export default function LikeButton() {
const [likes, setLikes] = useState(0);
return (
<button onClick={() => setLikes(likes + 1)}>
Likes: {likes}
</button>
);
}
Now import it into your Server Component:
JavaScript
import LikeButton from "./components/LikeButton";
export default function HomePage() {
return (
<main>
<h1>Server-rendered page</h1>
<LikeButton />
</main>
);
}
This keeps the page mostly server-rendered while sending only the interactive button JavaScript to the browser.
Step 4: Pass simple data to Client Components
A Server Component can pass props to a Client Component. Keep those props serializable, such as strings, numbers, booleans, arrays, and plain objects.
Server Component:
JavaScript
import UserCard from "./components/UserCard";
export default async function HomePage() {
const user = {
name: "Alex",
role: "Frontend developer"
};
return (
<main>
<UserCard user={user} />
</main>
);
}
Client Component:
JavaScript
"use client";
export default function UserCard({ user }) {
return (
<article>
<h2>{user.name}</h2>
<p>{user.role}</p>
</article>
);
}
Avoid passing functions, database connections, or class instances from Server Components to Client Components. Keep complex logic on the server and pass the final data the client needs.
Examples you can copy
Example 1: Server-rendered product list
JavaScript
async function getProducts() {
return [
{ id: 1, name: "HTML Course" },
{ id: 2, name: "CSS Course" },
{ id: 3, name: "React Course" }
];
}
export default async function ProductsPage() {
const products = await getProducts();
return (
<main>
<h1>Courses</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</main>
);
}
This page does not need client-side state, so it can stay as a Server Component.
Example 2: Server page with an interactive search box
Server Component:
JavaScript
import SearchBox from "./components/SearchBox";
export default function CoursesPage() {
return (
<main>
<h1>Find a course</h1>
<SearchBox />
</main>
);
}
Client Component:
JavaScript
"use client";
import { useState } from "react";
export default function SearchBox() {
const [query, setQuery] = useState("");
return (
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search courses"
/>
);
}
The page stays server-rendered, while the input runs in the browser.
Example 3: Fetch profile data on the server
JavaScript
async function getProfile() {
return {
name: "Sam",
completedLessons: 24
};
}
export default async function ProfilePage() {
const profile = await getProfile();
return (
<main>
<h1>{profile.name}</h1>
<p>Completed lessons: {profile.completedLessons}</p>
</main>
);
}
Use this pattern for read-only profile, dashboard, or content pages.
Common mistakes and how to fix them
Mistake 1: Using useState in a Server Component
What you might do:
JavaScript
import { useState } from "react";
export default function HomePage() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Why it breaks: Server Components render on the server. They cannot use client-side state or event handlers.
Correct approach:
JavaScript
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Move the interactive part into a Client Component.
Mistake 2: Adding "use client" too high in the tree
What you might do:
JavaScript
"use client";
import Header from "./Header";
import CourseList from "./CourseList";
export default function Page() {
return (
<>
<Header />
<CourseList />
</>
);
}
Why it breaks performance: Everything imported into this file becomes part of the client-side component tree. This can send more JavaScript to the browser than needed.
Correct approach:
JavaScript
import Header from "./Header";
import SearchBox from "./SearchBox";
import CourseList from "./CourseList";
export default function Page() {
return (
<>
<Header />
<SearchBox />
<CourseList />
</>
);
}
Put "use client" only inside SearchBox if that is the only interactive part.
Mistake 3: Passing non-serializable props
What you might do:
JavaScript
import UserCard from "./components/UserCard";
export default function Page() {
const user = new User("Alex");
return <UserCard user={user} />;
}
Why it breaks: Client Components need props that can be sent safely from the server to the browser. Class instances and functions are not good values to pass across that boundary.
Correct approach:
JavaScript
const user = {
name: "Alex"
};
return <UserCard user={user} />;
Pass plain data instead.
Troubleshooting
If React says hooks are not supported, move the hook into a Client Component.
If an event handler does not work, check that the component file starts with "use client".
If browser APIs like window or document are undefined, the code is running on the server.
If too much JavaScript loads in the browser, move "use client" lower in the component tree.
If data does not update as expected, check your framework’s caching and revalidation settings.
If props fail to pass into a Client Component, make sure they are serializable.
Quick recap
- Server Components render on the server and need no
"use client"directive. - Use them for data fetching, read-only UI, and server-only logic.
- Move state, effects, event handlers, and browser APIs into Client Components.
- Add
"use client"only to the smallest interactive component. - Pass simple serializable data across the server-client boundary.
- Use a React framework, such as Next.js App Router, to work with Server Components.
Join 35M+ people learning for free on Mimo
4.8 out of 5 across 1M+ reviews
Check us out on Apple AppStore, Google Play Store, and Trustpilot