Technology · Next.js
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.
Server Components (Default)
- Server components render on the server only.
// app/page.tsx export default async function Home() { const res = await fetch("https://api.example.com/data"); const data = await res.json(); return <div>{data.title}</div>; } - Server components can fetch data and access secrets.
- No JavaScript sent to the browser for these components.
- Can use async/await directly in the component.
- Access environment variables and databases without exposing them.
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 event handlers or browser APIs.
// This would cause an error in a server component: // const [count, setCount] = useState(0); // ❌ hooks not allowed
Client Components
- Mark components to render on the client with "use client".
"use client"; import { useState } from "react"; export default function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>{count}</button>; } - Must use "use client" at the top of the file.
- Can use hooks like useState, useEffect, useContext.
- Cannot access server-only resources.
- Use for interactive features like forms, modals, and tabs.
"use client"; export function Tabs({ items }) { const [active, setActive] = useState(0); return ( <div> {items.map((item, i) => ( <button key={i} onClick={() => setActive(i)}>{item.label}</button> ))} <div>{items[active].content}</div> </div> ); } - The directive affects the file and all its imports.
"use client"; // All imports here become part of the client bundle import { Chart } from "./Chart"; // also becomes client-side
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 }; }
Mixing Server and Client
- Pass server components as children to client components.
"use client"; export default function Layout({ children }) { return <div>{children}</div>; } // In a server component: <Layout><ServerSidebar /></Layout> - Fetch data in a server component and pass it as a prop.
// Server component export default async function Page() { const data = await getData(); return <ClientChart data={data} />; } - Client components can import server components as children.
- Use server components as high-level wrappers to keep fetching centralized.
export default async function BlogLayout({ children }) { const categories = await getCategories(); return ( <div> <ServerNav categories={categories} /> {children} </div> ); } - Keep the "use client" boundary as low in the tree as possible.
// Good: only the button is a client component export default async function Post() { const post = await getPost(); return <article>{post.body}<LikeButton id={post.id} /></article>; }
Best Practices
- 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
Tip: Use server components for fetching data and accessing secrets — it's faster and more secure than client components.
Warning: "use client" at the top of a file makes the entire file and its imports client-side — keep client-heavy components in separate files.