
When to Use the "use client" Directive in Next.js?
The "use client" directive is one line at the top of a file, and it is the most consequential line in an App Router project. Add it in the wrong place and you ship far more JavaScript than you need, lose the ability to fetch data directly from the server, and risk leaking server-only code into the browser. Leave it out where it is needed and the build fails with errors about useState or onClick. Many teams settle the question by putting "use client" at the top of almost every file, which works, but throws away most of what the App Router offers.
The rule is simpler than it looks. Every component in the app directory is a Server Component by default. You only need "use client" at the point where the component tree first needs something that only exists in the browser: state, effects, event handlers, browser APIs, or context. Everything imported below that point becomes client code automatically.
This article covers what the directive actually does, the specific cases that require it, the cases where people add it unnecessarily, how to push the boundary down the tree, how to pass data and Server Components across it, how to handle third-party libraries and context providers, and how to protect server-only code from ending up in the browser.
What "use client" Actually Does
In the App Router, components belong to one of two environments:
| Server Component (default) | Client Component ("use client") | |
|---|---|---|
| Renders on the server | Yes | Yes, for the initial HTML |
| Code sent to the browser | No | Yes |
| Hydrates and re-renders in the browser | No | Yes |
Can be async and await data | Yes | No |
| Can read databases, files, secrets | Yes | No |
Can use useState, useEffect, event handlers | No | Yes |
Can use browser APIs such as window | No | Yes |
A common misconception is that "use client" means "render only in the browser." It does not. Client Components still render on the server to produce HTML on the first visit, and then hydrate in the browser. The directive controls whether the component's code is included in the client JavaScript bundle and runs in the browser, not whether it appears in the server HTML.
The directive also marks a boundary in the module graph, not just a single component. When a file starts with "use client", the components it exports become entry points to the client. Every module that file imports, and every module those import, is bundled for the browser as well. You do not need to repeat the directive in each child file.
// app/components/counter.tsx
"use client";
import { useState } from "react";
import { formatCount } from "@/lib/format"; // also bundled for the client
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {formatCount(count)}
</button>
);
}
The directive must be the first statement in the file, before any imports. Comments above it are fine; code is not.
When You Need "use client"
Add the directive to a component file when the component itself uses any of the following.
1. State and Lifecycle Hooks
useState, useReducer, useEffect, useLayoutEffect, useRef for DOM access, useTransition, useOptimistic, and useActionState all depend on a component instance that lives and re-renders in the browser.
// app/components/newsletter-toggle.tsx
"use client";
import { useState } from "react";
export function NewsletterToggle() {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen((o) => !o)} aria-expanded={open}>
Subscribe to the newsletter
</button>
{open && <p>Enter your email below to get one useful article a week.</p>}
</div>
);
}
2. Event Handlers
onClick, onChange, onSubmit with a JavaScript function, onMouseEnter, and other handlers are functions that run in the browser. Server Components cannot attach them. The error message is explicit: "Event handlers cannot be passed to Client Component props."
3. Browser APIs
Anything that touches window, document, localStorage, navigator, IntersectionObserver, matchMedia, the clipboard, or geolocation needs to run in the browser, and must be called inside an effect or event handler, not during render, to avoid hydration mismatches. Our guide on how to fix hydration errors in Next.js explains why.
// app/components/copy-button.tsx
"use client";
import { useState } from "react";
export function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = useState(false);
async function copy() {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
return <button onClick={copy}>{copied ? "Copied" : "Copy"}</button>;
}
4. Client-Side Navigation Hooks
useRouter, usePathname, useSearchParams, useParams, and useSelectedLayoutSegment from next/navigation only work in Client Components. In a Server Component page, read params and searchParams from props instead.
5. React Context
createContext providers and useContext consumers require Client Components. Server Components cannot read context at all.
6. Class Components
Class components with lifecycle methods are client-only. They are rare in new code but common in older libraries.
When You Do Not Need "use client"
These cases come up constantly and do not require the directive:
- Fetching data. Server Components can be
asyncandawaita database query or afetchcall directly. Moving data fetching into a Client Component means an extra round trip and usually an API route. - Rendering static markup. Headers, footers, article bodies, product descriptions, pricing tables, and marketing sections that do not respond to interaction are Server Components.
- Using
next/linkandnext/image. Both work in Server Components.Linkhandles client-side navigation and prefetching internally. - Forms that submit to a Server Action. A form whose
actionprop is a Server Function works in a Server Component, even without JavaScript loaded. You only need a Client Component for pending states, optimistic UI, or field-level validation as the user types. - Native interactive HTML. A
detailselement opens and closes, avideowithcontrolsplays, and an anchor link scrolls, all without any React state. - Reading cookies and headers.
cookies()andheaders()fromnext/headersare server functions. They do not work in Client Components. - Page and layout files, by default.
page.tsxandlayout.tsxshould usually stay Server Components so they can exportmetadata, fetch data, and keep the bundle small. Themetadataexport andgenerateMetadataare not supported in files marked with"use client".
Here is a form that needs no client code:
// app/contact/page.tsx
import { sendMessage } from "./actions";
export default function ContactPage() {
return (
<form action={sendMessage}>
<label>
Email
<input name="email" type="email" required />
</label>
<label>
Message
<textarea name="message" required />
</label>
<button type="submit">Send</button>
</form>
);
}
// app/contact/actions.ts
"use server";
export async function sendMessage(formData: FormData) {
const email = String(formData.get("email"));
const message = String(formData.get("message"));
// Save the message or send an email here
console.log({ email, message });
}
Notice the different directive. "use server" marks Server Functions that can be called from the client. It is not the opposite of "use client" and is not used to mark Server Components, which need no directive at all.
Push the Boundary Down the Tree
The most important habit is to put "use client" on the smallest component that needs it, not on the page or layout that contains it.
Here is the common mistake: a whole page becomes a Client Component because one search box needs state.
// app/blog/page.tsx (too much client code)
"use client";
import { useState, useEffect } from "react";
type Post = { slug: string; title: string; excerpt: string };
export default function BlogPage() {
const [posts, setPosts] = useState<Post[]>([]);
const [query, setQuery] = useState("");
useEffect(() => {
fetch("/api/posts")
.then((r) => r.json())
.then(setPosts);
}, []);
const visible = posts.filter((p) =>
p.title.toLowerCase().includes(query.toLowerCase()),
);
return (
<main>
<h1>Blog</h1>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search"
/>
{visible.map((p) => (
<article key={p.slug}>
<h2>{p.title}</h2>
<p>{p.excerpt}</p>
</article>
))}
</main>
);
}
This version needs an API route, shows an empty page until the fetch completes, ships the rendering code for every post to the browser, and cannot export metadata.
The improved version keeps the page on the server and isolates the interactive part:
// app/blog/page.tsx
import type { Metadata } from "next";
import { getPosts } from "@/lib/posts";
import { PostSearch } from "./post-search";
export const metadata: Metadata = { title: "Blog" };
export default async function BlogPage() {
const posts = await getPosts();
return (
<main>
<h1>Blog</h1>
<PostSearch
posts={posts.map(({ slug, title, excerpt }) => ({
slug,
title,
excerpt,
}))}
/>
</main>
);
}
// app/blog/post-search.tsx
"use client";
import Link from "next/link";
import { useState } from "react";
type PostSummary = { slug: string; title: string; excerpt: string };
export function PostSearch({ posts }: { posts: PostSummary[] }) {
const [query, setQuery] = useState("");
const visible = posts.filter((p) =>
p.title.toLowerCase().includes(query.toLowerCase()),
);
return (
<>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search"
/>
{visible.map((p) => (
<article key={p.slug}>
<h2>
<Link href={`/blog/${p.slug}`}>{p.title}</Link>
</h2>
<p>{p.excerpt}</p>
</article>
))}
</>
);
}
The posts are in the initial HTML, there is no API route, the page can export metadata, and the client bundle contains only the search component. Note that the page also maps the posts down to the three fields the client needs, rather than sending entire database records.
Passing Data Across the Boundary
Everything you pass from a Server Component to a Client Component as a prop is serialized into the page payload and sent to the browser. That has two consequences.
Props must be serializable. Strings, numbers, booleans, null, plain objects, arrays, Date, Map, Set, typed arrays, Promises, and React elements are allowed. Functions, class instances, and symbols are not, with one exception: Server Functions marked with "use server" can be passed as props, because they cross as references.
// This throws: functions cannot be passed to Client Components
<LikeButton onLike={() => console.log("liked")} />;
// This works: a Server Function crosses as a reference
import { likePost } from "./actions";
<LikeButton onLike={likePost} />;
Props are public. Anything in a prop is visible in the page source. Do not pass a full user record that includes a password hash, internal IDs, or API keys just because the component needs the user's name. Select the fields you need on the server.
Passing Server Components into Client Components
A Client Component cannot import a Server Component; anything it imports becomes client code. But it can render Server Components that are passed to it as children or other props. The Server Component renders on the server, and the Client Component only receives its output.
This pattern is how you build interactive wrappers, such as tabs, modals, accordions, and carousels, around content that stays on the server:
// app/components/collapsible.tsx
"use client";
import { useState, type ReactNode } from "react";
export function Collapsible({
title,
children,
}: {
title: string;
children: ReactNode;
}) {
const [open, setOpen] = useState(false);
return (
<section>
<button onClick={() => setOpen((o) => !o)} aria-expanded={open}>
{title}
</button>
{open && <div>{children}</div>}
</section>
);
}
// app/product/[id]/page.tsx
import { Collapsible } from "@/app/components/collapsible";
import { Reviews } from "./reviews"; // async Server Component that queries the database
export default async function ProductPage({
params,
}: PageProps<"/product/[id]">) {
const { id } = await params;
return (
<main>
<Collapsible title="Customer reviews">
<Reviews productId={id} />
</Collapsible>
</main>
);
}
Reviews runs on the server, can query the database, and adds no JavaScript to the bundle. Collapsible only handles the open and closed state.
Context Providers
Providers need "use client", but your layout does not. Wrap the provider in its own Client Component and render it from the layout:
// app/providers.tsx
"use client";
import { createContext, useContext, useState, type ReactNode } from "react";
type Cart = { count: number; add: () => void };
const CartContext = createContext<Cart | null>(null);
export function CartProvider({ children }: { children: ReactNode }) {
const [count, setCount] = useState(0);
return (
<CartContext.Provider value={{ count, add: () => setCount((c) => c + 1) }}>
{children}
</CartContext.Provider>
);
}
export function useCart() {
const cart = useContext(CartContext);
if (!cart) throw new Error("useCart must be used inside CartProvider");
return cart;
}
// app/layout.tsx
import { CartProvider } from "./providers";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<CartProvider>{children}</CartProvider>
</body>
</html>
);
}
Because children is passed through rather than imported, pages under the provider remain Server Components. Render providers as deep as possible: a provider only used in the dashboard belongs in app/dashboard/layout.tsx, not the root layout.
Third-Party Components Without the Directive
Many npm packages use hooks but do not include "use client" in their files, because they were written before Server Components existed. Importing them directly into a Server Component fails. The fix is a one-line wrapper file:
// app/components/carousel.tsx
"use client";
export { Carousel } from "acme-carousel";
Now any Server Component can import Carousel from your wrapper. If you maintain a component library yourself, add "use client" to the entry points that use client-only features so your users do not need wrappers. For more on integrating packages, see how to use third-party libraries and plugins with Next.js.
Protecting Server-Only Code
Because client imports are transitive, it is possible to accidentally import a module that reads a secret or talks to the database into a Client Component. Environment variables without the NEXT_PUBLIC_ prefix are replaced with empty strings in the client bundle, so the code silently breaks instead of failing loudly.
Mark such modules with the server-only package. Any attempt to import them into client code becomes a build error:
// lib/db.ts
import "server-only";
import { Pool } from "pg";
export const db = new Pool({ connectionString: process.env.DATABASE_URL });
The client-only package does the reverse for modules that must never run on the server, such as code that touches window at import time. Next.js handles both imports internally, so installing the packages is optional unless your linter requires declared dependencies.
A Quick Decision Checklist
Ask these questions about a component, in order:
- Does it use state, effects, refs to DOM nodes, or event handler functions? Add
"use client". - Does it use browser APIs,
next/navigationhooks, or React context? Add"use client". - Is it a third-party component that uses hooks but has no directive? Wrap it in a
"use client"file. - Is it a page or layout? Keep it on the server and extract the interactive part into a child component.
- Does it only render markup, links, images, or data it fetches? No directive.
- Is it already imported by a Client Component? No directive needed; it is client code already. Add one only if Server Components also render it directly as an entry point.
Common Problems and Fixes
- "You're importing a component that needs useState. This React Hook only works in a Client Component." Add
"use client"to that component's file, not to the page that imports it. - "Event handlers cannot be passed to Client Component props." A Server Component passed an inline function. Move the handler into the Client Component, or pass a Server Function instead.
- "Only plain objects can be passed to Client Components from Server Components." You passed a class instance, such as a database row object or a Decimal. Convert it to a plain object first.
metadataexport ignored or erroring. The page has"use client". Remove it and move the interactive code into a child component.- Environment variable is empty in a component. The component is client code and the variable is not prefixed with
NEXT_PUBLIC_. Read it in a Server Component and pass only what is safe as a prop. - Bundle is larger than expected. A
"use client"file high in the tree imports large dependencies. Push the directive down, or load heavy pieces withnext/dynamic.
use client FAQ
No. Client Components still render on the server to produce the initial HTML, and then hydrate in the browser. The directive means the component code is also sent to and runs in the browser, so it can use state, effects, and event handlers.
No. Every module imported by a file marked with use client automatically becomes part of the client bundle. You only need the directive in files whose components are rendered directly by Server Components.
No. Server Components need no directive because they are the default. The use server directive marks Server Functions, such as Server Actions, which are server-side functions that Client Components and forms can call.
Yes, if the Server Component is passed in as children or another prop from a Server Component parent. A Client Component cannot import a Server Component directly, because anything it imports becomes client code.
It is allowed, but it is rarely the best choice. A client page cannot export metadata, cannot fetch data directly on the server, and sends all of its code to the browser. Keep pages as Server Components and move interactive pieces into small child components.
The Pages Router ignores it because every page component there already runs in the browser. The directive only has meaning in the app directory, where components are Server Components by default.
Conclusion
"use client" marks the point where your component tree crosses from the server into the browser. Everything above that point runs only on the server and costs the user nothing in JavaScript; everything below it is bundled, downloaded, and hydrated. Using it well comes down to placing that boundary as low and as narrowly as possible.
Add the directive to components that use state, effects, event handlers, browser APIs, navigation hooks, or context, and to thin wrappers around third-party components that need them. Leave pages, layouts, data fetching, and static markup on the server. Pass only serializable, safe data across the boundary, pass Server Components into Client Components as children when you need interactive wrappers, and mark server-only modules so they can never leak. If you are moving an older project to this model, our guide on migrating from the Pages Router to the App Router walks through it step by step.
Here are some useful references for going deeper on the client boundary:
- Next.js Docs: use client directive — the API reference for the directive in Next.js.
- Next.js Docs: Server and Client Components — when to use each and how they compose.
- React Docs: 'use client' — the underlying React feature, including the full list of serializable prop types.
- React Docs: 'use server' — how Server Functions cross the boundary.
- Next.js Docs: Data Security — how to avoid exposing sensitive data in props sent to the client.


