Skip to main content

Server and Client Components in App Router

The App Router runs React components on the server by default. Server Components execute only on the server, fetch data directly from databases, use API keys safely, and send zero JavaScript to the browser—dramatically improving performance. Client Components (marked with "use client") run in the browser, use React hooks, and interact with the DOM. Understanding this distinction is critical for building efficient Next.js apps. This article covers when to use each and how they interact.

What Are Server Components?

Server Components are React components that render exclusively on the server. They don't support React hooks (like useState or useEffect), don't execute browser APIs (like localStorage or window), and send no JavaScript bundle to the client—only the rendered HTML. They're ideal for fetching data, connecting to databases, and hiding secrets.

A Server Component is any component file without a "use client" directive. Here's an example:

// app/posts/page.tsx (Server Component by default)
import db from "@/lib/database";

export default async function PostsPage() {
const posts = await db.query("SELECT * FROM posts");

return (
<div>
<h1>Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}

Notice:

  • The component is async (server-only feature)
  • It directly queries the database
  • No API key or secret is exposed to the browser
  • The resulting HTML has no JavaScript overhead

What Are Client Components?

Client Components (marked with "use client") are traditional React components that run in the browser. They support hooks, can access browser APIs, and handle interactivity. Mark a component with "use client" to opt into client-side rendering:

// app/counter.tsx
"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>
);
}

This component:

  • Uses useState (client-only hook)
  • Handles clicks interactively
  • Runs JavaScript in the browser
  • Can't access databases or secrets

When to Use Each

FeatureServer ComponentClient Component
Fetch dataYesNo (use API)
Access databasesYesNo
Use API keysYes (safe)No (exposed)
Use hooksNoYes
Access windowNoYes
Handle click eventsNoYes
Read browser storageNoYes

Use Server Components for:

  • Fetching and transforming data
  • Accessing databases or private APIs
  • Storing secrets (API keys, tokens)
  • Heavy computations (rendering markdown, templates)

Use Client Components for:

  • Interactivity (buttons, forms, clicks)
  • React hooks (useState, useEffect, custom hooks)
  • Browser APIs (localStorage, geolocation, Notification)
  • Event listeners and real-time updates

Composing Server and Client Components

Servers and Client Components work together. A Server Component can import a Client Component, fetch data, and pass it as a prop:

// app/blog/page.tsx (Server Component)
import PostCard from "./post-card";
import db from "@/lib/database";

export default async function BlogPage() {
const posts = await db.query("SELECT * FROM posts");

return (
<div>
<h1>Blog</h1>
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
</div>
);
}
// app/blog/post-card.tsx (Client Component)
"use client";

import { useState } from "react";

export default function PostCard({ post }: { post: any }) {
const [liked, setLiked] = useState(false);

return (
<article className="border rounded p-4 mb-4">
<h2 className="text-xl font-bold">{post.title}</h2>
<p>{post.excerpt}</p>
<button
onClick={() => setLiked(!liked)}
className="mt-4 bg-blue-600 text-white px-4 py-2 rounded"
>
{liked ? "Unlike" : "Like"}
</button>
</article>
);
}

The BlogPage Server Component fetches posts from the database and passes them to PostCard, a Client Component that handles likes. This pattern combines efficiency with interactivity.

The "Use Client" Boundary

When you mark a component with "use client", that component and all its children become client-side—even if the children don't use hooks. This is called the client boundary. Keep client boundaries at the leaves (interactive elements) to minimize the JavaScript sent to the browser.

Bad: Marking the entire page as client:

// app/page.tsx
"use client";

import { useState } from "react";
import db from "@/lib/database"; // ❌ Can't use in client component!

export default function HomePage() {
// ...
}

Good: Marking only the interactive button as client:

// app/page.tsx (Server Component)
import LikeButton from "./like-button";

export default async function HomePage() {
const data = await db.query("SELECT * FROM posts");

return (
<div>
<h1>Home</h1>
{data.map((item) => (
<div key={item.id}>
<h2>{item.title}</h2>
<LikeButton itemId={item.id} />
</div>
))}
</div>
);
}
// app/like-button.tsx (Client Component)
"use client";

import { useState } from "react";

export default function LikeButton({ itemId }: { itemId: number }) {
const [liked, setLiked] = useState(false);

return (
<button onClick={() => setLiked(!liked)}>
{liked ? "Unlike" : "Like"}
</button>
);
}

Now most of the page is server-rendered (fast, data-driven), and only the button is client-side (interactive).

Fetching Data in Client Components

Client Components can't fetch data during render. Instead, fetch data from Server Components or use Client-side APIs:

// app/products/page.tsx (Server)
import ProductList from "./product-list";

async function getProducts() {
const res = await fetch("https://api.example.com/products");
return res.json();
}

export default async function ProductsPage() {
const products = await getProducts();

return <ProductList initialProducts={products} />;
}
// app/products/product-list.tsx (Client)
"use client";

import { useState } from "react";

export default function ProductList({
initialProducts,
}: {
initialProducts: any[];
}) {
const [products, setProducts] = useState(initialProducts);
const [filter, setFilter] = useState("");

const filtered = products.filter((p) =>
p.name.toLowerCase().includes(filter.toLowerCase())
);

return (
<div>
<input
type="text"
placeholder="Filter..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
<ul>
{filtered.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</div>
);
}

Option 2: Fetch in useEffect (for dynamic updates)

// app/search.tsx (Client)
"use client";

import { useState, useEffect } from "react";

export default function Search() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);

useEffect(() => {
if (!query) {
setResults([]);
return;
}

setLoading(true);
fetch(`/api/search?q=${query}`)
.then((res) => res.json())
.then((data) => {
setResults(data.results);
setLoading(false);
});
}, [query]);

return (
<div>
<input
type="text"
placeholder="Search..."
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
{loading && <p>Loading...</p>}
<ul>
{results.map((result) => (
<li key={result.id}>{result.title}</li>
))}
</ul>
</div>
);
}

Common Patterns

Pattern 1: Server Fetches, Client Displays

// Server fetches data, Client displays with interactivity
export default async function Page() {
const data = await fetchData();
return <ClientComponent data={data} />;
}

Pattern 2: Form in Client, Handler in Server

// app/submit-form.tsx
"use client";

import { createPost } from "@/app/actions";

export default function SubmitForm() {
return (
<form action={createPost}>
<input name="title" placeholder="Title" />
<button type="submit">Create</button>
</form>
);
}
// app/actions.ts
"use server";

export async function createPost(formData: FormData) {
const title = formData.get("title");
await db.insert({ title });
redirect("/posts");
}

Key Takeaways

  • Server Components (default) run on the server only, fetch data directly, and send no JavaScript. Use for data fetching and logic.
  • Client Components ("use client") run in the browser, support hooks, and handle interactivity.
  • Mark boundaries strategically—put "use client" only on interactive components to minimize client-side JavaScript.
  • Server Components can import Client Components and pass props; Client Components can't import Server Components.
  • Fetch in Server Components, pass data as props to Client Components, or fetch in useEffect for dynamic updates.

Frequently Asked Questions

Can Server Components use async/await?

Yes. Server Components are inherently async; you can use async/await directly in the component function without extra setup.

Can a Client Component import a Server Component?

No. Client Components can't import Server Components because the server component code might reference databases or secrets. The build fails with a clear error. Instead, pass server-rendered content as children or props.

Does "use client" make my entire page client-rendered?

Yes. When you add "use client" to a component, that component and all its children are client-rendered. To keep most of your page server-rendered, put "use client" only on leaf components (buttons, forms, interactive elements).

Why would I ever use Client Components if Server Components are more efficient?

For interactivity. Server Components can't handle clicks, track state, or respond to user input. For a button that increments a counter, you need a Client Component. For a page that fetches and displays data, use a Server Component.

Can I call a Server Component function from a Client Component?

Not directly. But you can use "Server Actions"—functions marked with "use server" that Client Components can call. See the pattern in the FAQ above.

Further Reading