When to use Server Components, Client Components, and Server Actions. Includes a decision tree with concrete examples.
When to use Server Components, Client Components, and Server Actions. Includes a decision tree with concrete examples.
BeforeMerge offers hundreds of code review rules, guides, and detection patterns to help your team ship better code.
The App Router introduces a new mental model for rendering. Understanding when to use each strategy is essential for performance and developer experience.
Every component in the App Router is a Server Component by default. They render on the server and send HTML to the client.
Use when:
// app/posts/page.tsx — Server Component (default)
export default async function PostsPage() {
const posts = await db.post.findMany();
return (
<ul>
{posts.map((p) => <li key={p.id}>{p.title}</li>)}
</ul>
);
}Cannot: Use hooks, browser APIs, event handlers, or state.
Opt in with "use client" at the top of the file.
Use when:
"use client";
import { useState } from "react";
export function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>
{liked ? "Unlike" : "Like"}
</button>
);
}Key rule: Client Components can import other Client Components, but they cannot import a Server Component directly. Instead, pass Server Components as children or other props.
Functions that run on the server but can be called from Client Components.
Use when:
// app/actions.ts
"use server";
export async function createPost(formData: FormData) {
const title = formData.get("title") as string;
await db.post.create({ data: { title } });
revalidatePath("/posts");
}Keep the Client Component boundary as small as possible:
// Server Component (page)
export default async function Page() {
const data = await fetchData();
return (
<section>
<h1>{data.title}</h1>
<InteractiveWidget initialData={data.items} />
</section>
);
}This fetches data on the server but delegates interactivity to a small Client Component.