Next.js Directives
Use use server and use client directives to control rendering and data access.
TL;DR
- 01"use client" marks components to render on the client.
- 02"use server" marks functions to run only on the server.
- 03Server components are the default in the App Router.
Tips
- 01Use server components for fetching data and accessing secrets — it's faster and more secure than client components.
Warnings
- 01"use client" at the top of a file makes the entire file and its imports client-side — keep client-heavy components in separate files.
Default Server Rendering
- Every component in the App Router is a Server Component by default — no directive needed.
// app/page.tsx — server component, no directive required export default async function Home() { const res = await fetch("https://api.example.com/data", { cache: "no-store" }); const data = await res.json(); return <div>{data.title}</div>; } - Server Components run only on the server — no JavaScript is shipped to the browser for them.
- They can use async/await, read environment variables, and query databases securely.
export default async function AdminPage() { const users = await db.user.findMany(); return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>; } - Server Components cannot use React hooks or event handlers — add a directive to opt into client rendering only when needed.
// ❌ Not allowed in a Server Component — move to a Client Component // const [count, setCount] = useState(0); // window.addEventListener(...); - Use React cache() to deduplicate repeated server-side data calls across components.
import { cache } from "react"; export const getUser = cache(async (id: string) => db.user.findUnique({ where: { id } })); // getUser(id) called in Header and Sidebar = only one DB query
Client Directive Scope
- "use client" must be the very first line of the file — before any imports.
"use client"; // ↑ must appear before any import statements import { useState } from "react"; export default function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>{count}</button>; } - The directive marks the entire file AND all modules it imports as client-side — contamination spreads through imports.
"use client"; // Both files below become part of the client bundle: import { Chart } from "./Chart"; // ← client-side import { DataGrid } from "./DataGrid"; // ← client-side - Avoid importing heavy utilities into a "use client" file — they inflate the browser bundle.
"use client"; // ❌ Pulls entire lodash into the client bundle import _ from "lodash"; // ✅ Move heavy computation to a Server Component or Server Action - Split a large client component into smaller files to minimise the client boundary.
// Good: only the interactive button is a Client Component // app/post/page.tsx — Server Component (no directive) import { LikeButton } from "./LikeButton"; // only this file has "use client" export default async function PostPage() { const post = await getPost(); return <article>{post.body}<LikeButton id={post.id} /></article>; } - Third-party components that use hooks or browser APIs must be wrapped with "use client".
// components/MapWrapper.tsx "use client"; import { MapComponent } from "some-map-library"; // uses window internally export default function MapWrapper({ center }) { return <MapComponent center={center} />; }
Server Functions
- Mark functions to run only on the server with "use server".
"use server"; export async function submitForm(formData: FormData) { const email = formData.get("email"); // Save to database securely return { success: true }; } - Server functions can be called from client components.
"use client"; async function handleSubmit(formData: FormData) { const result = await submitForm(formData); } - Functions run securely on the server.
- Use server functions with HTML form actions for progressive enhancement.
// app/form.tsx (server component) import { saveContact } from "./actions"; export default function ContactForm() { return <form action={saveContact}><input name="email" /><button>Send</button></form>; } - Validate input inside server functions before writing to the database.
"use server"; export async function createPost(formData: FormData) { const title = formData.get("title") as string; if (!title || title.length < 3) return { error: "Title too short" }; await db.post.create({ data: { title } }); return { ok: true }; }
Directive Boundary Patterns
- A Client Component can receive a Server Component as a child via props — the child still runs on the server.
"use client"; // ClientShell.tsx — the children prop can be a Server Component export default function ClientShell({ children }) { return <div>{children}</div>; } // app/page.tsx (Server Component) — ServerSidebar stays on the server <ClientShell><ServerSidebar /></ClientShell> - A Client Component cannot directly import a Server Component — this would pull server code into the client bundle.
"use client"; // ❌ Cannot import a server component directly from a client file import { ServerSidebar } from "./ServerSidebar"; // runtime error - Functions cannot cross the server-client boundary as props — use Server Actions instead.
// ❌ Plain server function cannot be passed as a prop <ClientComp onClick={serverFunction} /> // ✅ Server Action CAN be passed as a prop "use server"; export async function deleteItem(id: string) { await db.item.delete({ where: { id } }); } // Then in a Server Component: <ClientComp action={deleteItem} /> - Only JSON-serialisable values can be passed as props across the server-client boundary.
// ✅ OK: strings, numbers, arrays, plain objects, Promises <ClientChart data={data} title="Sales" /> // ❌ Not OK: class instances, Maps, Sets, functions (unless Server Actions) <ClientComp handler={someClassInstance} /> - Verify the boundary by checking the Network tab — server component code should not appear in JS bundles.
# Open DevTools > Network > JS # Server-only modules (db, secrets) should NOT appear in client chunks
Directive Design Patterns
- Use server components by default for performance.
// Good: server component fetches data export default async function Posts() { const posts = await getPosts(); return posts.map(post => <Post key={post.id} post={post} />); } - Mark leaf components as "use client" for interactivity.
"use client"; // Only interactive components export function Favorite({ postId }) { const [liked, setLiked] = useState(false); return <button onClick={() => setLiked(!liked)}>{liked ? "Liked" : "Like"}</button>; } - Use server-only package to prevent accidental client imports.
import "server-only"; // This file throws an error if imported in a client bundle export async function getSecretData() { ... } - Avoid passing non-serializable values like functions as props.
// Wrong: functions can't cross the server-client boundary as props <ClientComp onClick={serverFunction} /> // ❌ // Use server actions instead <ClientComp action={serverAction} /> // ✅ - Test directive placement by checking the Network tab for unexpected JS.
# Open browser DevTools > Network > JS # Check that server-only components don't appear in client bundles
FAQ
Add 'use client' when your component uses browser APIs, React hooks like useState or useEffect, or event listeners. Components that only fetch data or render static content should stay as server components.
Yes — Server Components can import and render Client Components, but not the other way around. Pass Server Components as children or props to Client Components if you need to compose them.
Placing 'use server' at the top of a file marks all exported functions as Server Actions, while placing it inside an individual async function marks only that function. Use the inline form when you need just one Server Action inside a Client Component.
If the component accessing the secret has 'use client' or is imported by a Client Component, it runs on the client. Move secret access into a Server Component or Server Action where the code never leaves the server.
No — Server Components can call fetch or query a database directly without any directive, since they already run only on the server by default in the App Router. 'use server' is specifically for Server Actions (functions called from the client).