'use client'
Mark a module and its dependencies as client code, creating a boundary from server-rendered code.
'use client';
import { useState } from 'react';
import { formatDate } from './formatters';
import Button from './button';
export default function RichTextEditor({ timestamp, text }) {
// ...
}- Added in React 19.0
Return value
Nothing. It is a build-time marker: bundlers treat an import of this module from server code as the boundary between server-run and client-run code.
Errors
Throws when server-evaluated code imports a value from a 'use client' module that is neither a React component nor a supported serializable prop value.
Changelog
- PHP 19.0Directives became stable as part of the React Server Components model; previously they were available only in Canary releases with an RSC-capable bundler.
Put 'use client' on the first line of a file and the bundler marks that module — and every module it transitively imports — as client code. Everything in that subtree is sent to and run by the browser, not just components.
When to reach for it
Add it to the first file that needs something the server cannot do: useState, useEffect, an event handler, window, or a browser-only library. You do not add it to every file that uses those things — one directive at the boundary is enough, since imports below it are pulled onto the client automatically.
What trips people up
It must be the very first thing in the file, above all imports, in single or double quotes — backticks do not work, and neither does placing it after an import. In a module already reached from client code the directive is a no-op, which is why sprinkling it everywhere gives no error but silently widens the bundle.
Values crossing the boundary must be serializable: primitives, arrays, Maps, Sets, TypedArrays, Date, plain objects, JSX, Promises, and Server Functions. Class instances, plain functions and objects with a null prototype throw. A Client Component still renders on the server during SSR — "client" means it also hydrates in the browser, not "browser only", so window still needs an effect or a guard.
Example
// Counter.jsx
'use client'; // must be the first line, above imports
import { useState } from 'react';
import { formatCount } from './formatters'; // pulled onto the client too
function Display({ label, value }) {
return (
<p>
{label}: {formatCount(value)}
</p>
);
}
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<Display label="Clicks" value={count} />
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
</div>
);
}
// page.jsx — a Server Component, no directive
// import Counter from './Counter';
// export default function Page() {
// return <Counter />;
// }When to use it
- Add a directive to the one file holding a modal's open/close state, leaving the page a Server Component.
- Wrap a browser-only charting library so it and its dependencies are bundled behind a single boundary.
- Expose an interactive shell that accepts server-rendered JSX through children, keeping the subtree off the client.
Frequently asked questions
Where exactly does 'use client' have to go?
On the very first line of the file, above every import and any other code, written with single or double quotes — comments above it are fine, backticks are not. Anywhere else and it is parsed as a plain string expression and silently ignored.
Do I need 'use client' in every file that uses hooks?
No — one directive at the top of the boundary module is enough, because every module it imports is treated as client code automatically. Adding it to a module that is only ever imported from client code has no effect at all.
Does 'use client' mean the component skips server rendering?
No, Client Components are still rendered to HTML on the server during SSR and then hydrate in the browser. Code that touches window or document must therefore run inside useEffect or behind a typeof window check, exactly as in a non-RSC app.
More examples
Passing serializable props across the boundary
The commented-out line throws during render with a message naming the offending prop, because row is an instance of the ORM's model class. Picking the three fields off it produces a plain object whose values are a string, a string and a Date — all supported — so the editor receives them and updatedAt arrives as a real Date, not a string.
// page.jsx — Server Component
import db from './db';
import NoteEditor from './NoteEditor';
export default async function Page() {
const row = await db.notes.get('welcome'); // a class instance from the ORM
// Throws: class instances are not serializable.
// return <NoteEditor note={row} />;
// Works: a plain object of primitives and a Date.
return (
<NoteEditor
note={{ id: row.id, title: row.title, updatedAt: row.updatedAt }}
/>
);
}
// NoteEditor.jsx
'use client';
import { useState } from 'react';
export default function NoteEditor({ note }) {
const [title, setTitle] = useState(note.title);
return (
<div>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<p>Last saved {note.updatedAt.toISOString()}</p>
</div>
);
}Where the directive belongs, and where it does nothing
formatters.js and Button.jsx carry no directive yet both end up in the client bundle, because they are transitive dependencies of a 'use client' module — the boundary is the import edge, not each file. The last block shows the common failure: a directive placed after an import is treated as an ordinary expression statement, so the module stays server-side and the first useState call fails at render.
// formatters.js — NO directive needed
export function formatDate(ts) {
return new Date(ts).toISOString().slice(0, 10);
}
// Button.jsx — NO directive needed
export default function Button({ children, onClick }) {
return <button onClick={onClick}>{children}</button>;
}
// Toolbar.jsx — the boundary lives here
'use client';
import { useState } from 'react';
import { formatDate } from './formatters'; // becomes client code
import Button from './Button'; // becomes client code
export default function Toolbar({ timestamp }) {
const [saved, setSaved] = useState(false);
return (
<div>
<span>{formatDate(timestamp)}</span>
<Button onClick={() => setSaved(true)}>Save</Button>
{saved && <span> Saved</span>}
</div>
);
}
// WRONG: the directive is ignored here.
// import { useState } from 'react';
// 'use client'; <- not the first statement, silently has no effect
Discussion