Client Components
Learn what "use client" really marks in Next.js: where the browser part starts, how Client Components still render on the server, and why it belongs low.
- 01Place the boundaryTell which files a "use client" line brings into the browser, and put it on the smallest part that needs it.
- 02Explain both rendersSay what a Client Component does on the server during the build, and what it does in the browser when React hydrates it.
- 03Use the browser safelyKeep state, Effects and browser APIs where they work, and wrap a package's component that lacks the directive.
A cup counter that works in the browser
Server Components in Next.js kept everything on the server. Now the tea shop's Mint tea page gets a cup counter: two buttons, and a total that changes as you click. That needs code running in the browser after the page is on the screen, so it needs a Client Component: a component whose code also goes to the browser, where it can keep state and respond to clicks. In Next.js, you start one with "use client" at the top of a file. Why Next.js? used that line for an Add to cart button. That line marks where the client part of the app starts, and everything the file imports goes to the browser with it.
Here is the page, built and run with Next.js 16.4.3. The recorder also opened it in real Chrome and clicked One more twice. The Steps tab is open on the first click.
app/cup-picker.tsx"use client"; import { useState } from "react"; export default function CupPicker({ price }: { price: number }) { const [cups, setCups] = useState(1); console.log(`CupPicker rendered with ${cups} cup`); return ( <p> <button onClick={() => setCups(cups - 1)} disabled={cups === 1}> One less </button>{" "} {cups} {cups === 1 ? "cup" : "cups"}: ${cups * price}{" "} <button onClick={() => setCups(cups + 1)}>One more</button> </p> );}Each step was taken in real Chrome against next start. Pick one to see the page after it.
Fresh mint leaves, dried in the sun.
2 cups: $6
- Focus
- the button “One more”
- Scrolled
- 0 px down
No requests during this step: the page already had what it needed
In app/cup-picker.tsx, line 1 is "use client". Line 6 keeps cups in state, starting at 1, and line 7 prints a message each time CupPicker renders. Lines 11 to 13 are the One less button, disabled at 1 cup. Line 14 shows the count and the total, and line 15 is the One more button. In app/page.tsx, a Server Component, line 8 renders CupPicker with price={3}.
In the Steps tab, the first step opened the page, with one request: the page itself, the document. Each click on One more changed the text, to 2 cups: $6 and then 3 cups: $9, and made no request at all. This click only updates local state, which lives in the browser, and React updated the page there. (A handler that fetches data or calls a Server Function would still send a request.)
Now open the Terminal tab: CupPicker rendered with 1 cup is in next build's output. The Client Component ran on the server too, during the build, and the Browser tab shows the HTML it made: One less 1 cup: $3 One more. (It's printed twice for the reason the last lesson gave: with Cache Components on, the build renders a page twice.) The recording also searched the JavaScript files the page loads: they hold One more, from CupPicker's code, and not Fresh mint leaves, from the page's.
So CupPicker runs in two places. On the server, it renders to HTML. In the browser, React hydrates it: hydration is React running the Client Components again in the browser, matching them to the HTML already on the screen, and attaching their event handlers, so the buttons start to work.
CupPicker renders in two places. On the server, during next build, it makes the HTML. In the browser, React runs it again, matches the HTML, and attaches its onClick handlers. Shaded cards are code that goes to the browser.In a science museum, most exhibits are behind glass. One door leads to a hands-on room, and everything past that door can be touched, even the exhibits without a sign of their own. Staff set up the room before the museum opens, and switch the machines on when the first visitors walk in.
- In real life: Exhibits behind glass: you look, but you can't touch
- In Next.js: Server Components: the browser gets what they rendered
- In real life: The door marked "hands-on room"
- In Next.js: A file that starts with
"use client" - In real life: Everything past that door, even exhibits with no sign of their own
- In Next.js: Everything that file imports: it all goes to the browser
- In real life: Machines set up before opening, switched on when visitors arrive
- In Next.js: HTML rendered on the server, then hydrated in the browser
A file that starts with "use client" is where the client part of your app starts. Its components, and everything the file imports, also go to the browser. A Client Component still renders on the server first, into the HTML. Then React hydrates it in the browser, where it keeps state, runs Effects and handles events.
"use client" marks an entry point
Next.js's docs call a file with "use client" an entry point: the place where the client part of the app is entered from the server part. The line between the two parts is called the boundary. Two rules decide what's on the client side of it:
- The directive goes at the very top of the file, before the imports, and it applies to the whole file. A component can't be marked on its own.
- Every file an entry point imports is part of the client too, with or without the directive, and so is everything those files import.
So you don't put "use client" in every file of the client part, only in the files that Server Components render. Here, OrderBox is the entry point. It imports CupCounter from a file without the directive, which imports formatPrice from price.ts. The page imports price.ts too.
"use client";
import { useState } from "react";
import CupCounter from "./cup-counter";
export default function OrderBox({ price }: { price: number }) {
const [added, setAdded] = useState(false);
return (
<section>
<CupCounter price={price} />
<button onClick={() => setAdded(true)}>{added ? "Added" : "Add to cart"}</button>
</section>
);
}
import { useState } from "react";
import { formatPrice } from "./price";
export default function CupCounter({ price }: { price: number }) {
const [cups, setCups] = useState(1);
return (
<p>
{cups} {cups === 1 ? "cup" : "cups"}, {formatPrice(cups * price)}{" "}
<button onClick={() => setCups(cups + 1)}>One more</button>
</p>
);
}
In order-box.tsx, line 1 is the directive, line 4 imports CupCounter, and line 11 renders it. In cup-counter.tsx, line 1 imports useState with no directive anywhere in the file, and line 10 is the One more button. Try three versions of who imports CupCounter. Before you click, guess which one fails.
app/order-box.tsx"use client"; import { useState } from "react";import CupCounter from "./cup-counter"; export default function OrderBox({ price }: { price: number }) { const [added, setAdded] = useState(false); return ( <section> <CupCounter price={price} /> <button onClick={() => setAdded(true)}>{added ? "Added" : "Add to cart"}</button> </section> );}$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled Creating an optimized production build ...✓ Compiled successfully Running TypeScript ... Finished TypeScript ... Collecting page data ...✓ Generating static pages (3/3) Finalizing page optimization ... Route (app)┌ ○ /└ ○ /_not-found ○ (Static) prerendered as static contentLine 1 makes order-box.tsx an entry point, and line 4 imports CupCounter. cup-counter.tsx has no directive, but it's part of the client now. The build prints ○ /, and the page's JavaScript holds One more, from CupCounter.
In the first version, the recording searched the page's JavaScript. It holds One more, which is only in cup-counter.tsx, and Free today, which is only in price.ts: a text the page never shows, because the price is never 0. Neither file has the directive; they went to the browser because order-box.tsx imports them. The page's own text, Fresh mint leaves, isn't there.
In the second version, the page imports cup-counter.tsx itself. It's the same file, but now a Server Component imports it, so it's part of the server, and its useState stops the build. The import trace under the error shows the path: Server Component: ./app/cup-counter.tsx ./app/page.tsx. In the third version, cup-counter.tsx has its own directive, so it's an entry point too, and the page can render it.
order-box.tsx starts with "use client", so the client part starts there, and cup-counter.tsx and price.ts come along because it imports them. The page imports price.ts too, so Next.js also builds a copy of it for the server.A file can be on both sides. The page and CupCounter both import price.ts, so Next.js builds it once for the server and once for the browser. Only props cross the boundary as data: the page gives OrderBox price={3}, and like every prop from a Server Component, it travels in the RSC payload: React's own description of what the Server Components rendered, with the props they pass to Client Components. Which values can cross, and how to put Server Components inside Client Components, is the next lesson of this stage.
State, Effects and event handlers
Past the boundary, you have all of React: state, event handlers, Effects, refs, context, custom hooks, and the browser's own APIs, like window and localStorage. CupPicker used state and event handlers. An Effect runs after React has put the component on the screen, and only in the browser, which makes it the place to read data that only the browser has.
Here the Mint tea page gets a Save for later button that remembers the choice in localStorage: a small store the browser keeps for each site, which survives a reload. The recorder opened the page, clicked the button, then opened the page again. The Steps tab is open on the last step.
app/save-button.tsx"use client"; import { useEffect, useState } from "react"; export default function SaveButton({ id }: { id: string }) { const [saved, setSaved] = useState(false); useEffect(() => { setSaved(localStorage.getItem("saved-tea") === id); }, [id]); function save() { localStorage.setItem("saved-tea", id); setSaved(true); } return ( <button onClick={save} disabled={saved}> {saved ? "Saved" : "Save for later"} </button> );}Each step was taken in real Chrome against next start. Pick one to see the page after it.
Fresh mint leaves, dried in the sun.
- Focus
- nothing: the page itself
- Scrolled
- 0 px down
Requests the page made during this step (1), without the files it loaded
GET/200a whole page
In app/save-button.tsx, line 6 starts saved at false. Lines 8 to 10 are an Effect: once the button is on the screen, it reads localStorage and sets saved. Lines 12 to 15 are save, which the onClick on line 18 calls: it stores Mint tea's id and sets saved to true. Line 19 shows Saved or Save for later.
In the Steps tab, the first step shows Save for later. The click changed it to Saved and made no request. The last step opened the page again: a whole new page load, one document request, and the button says Saved. The server didn't know about the saved tea. Its HTML, in the Network tab, says Save for later, the same for every visitor. The Effect changed it, in the browser, after hydration.
Client Components still render on the server
"Client" doesn't mean "only in the client". Next.js's docs say a Client Component runs on the server and in the browser. On a first visit, it renders on the server into the HTML, during the build or for the request. Then React renders it again in the browser while it hydrates. The docs add that after a client-side navigation, like a click on a Link, the router gets only the RSC payload, and Client Components render in the browser alone. Step through the first visit to the Mint tea page, then the click:
Opening the pageafter step 1A blank page
React isn't running yet: clicks do nothing.
The page isn't complete yet.
Before any visitor comes, next build runs MintTeaPage and CupPicker on the server, with cups at 1, and keeps the HTML. CupPicker rendered with 1 cup is in the build's output.
After step 3 of Opening the page, the visitor sees the buttons, but clicking them does nothing until step 5. React runs only CupPicker in the browser: MintTeaPage is a Server Component, and it never hydrates. Each row below is one piece of CupPicker or SaveButton: what it does during the server render, and what it does in the browser.
| In the component | On the server | In the browser |
|---|---|---|
useState(1) | Starts at 1, and stays there: the HTML says 1 cup. | Starts at 1 again, then changes with each click. |
onClick={…} | Never called. The HTML has a plain <button>. | Attached when React hydrates the component. |
useEffect(…) | Never runs. | Runs after hydration, and again when its dependencies change. |
localStorage during render | Not defined: the build stops. | Defined, but the first render must still match the HTML. |
console.log during render | Printed in next build's output. | Printed in the browser's console. |
So the code that runs while a Client Component renders must work in both places, and give the same result in both. Try three places for SaveButton to read localStorage. Before you click, guess which one the build stops.
app/save-button.tsxNext.jsapp/save-button.tsx"use client"; import { useEffect, useState } from "react"; export default function SaveButton({ id }: { id: string }) { const [saved, setSaved] = useState(false); useEffect(() => { setSaved(localStorage.getItem("saved-tea") === id); }, [id]); function save() { localStorage.setItem("saved-tea", id); setSaved(true); } return ( <button onClick={save} disabled={saved}> {saved ? "Saved" : "Save for later"} </button> );}$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled Creating an optimized production build ...✓ Compiled successfully Running TypeScript ... Finished TypeScript ... Collecting page data ...✓ Generating static pages (3/3) Finalizing page optimization ... Route (app)┌ ○ /└ ○ /_not-found ○ (Static) prerendered as static contentLine 6 starts saved at false, on the server and in the browser alike. After hydration, the Effect on lines 8 to 10 reads localStorage. Open the Steps tab: after a reload, the button says Saved, with no errors.
The Effect version works, as above. The second version reads localStorage on line 6, while the component renders. The build renders it on the server, where localStorage doesn't exist, and stops with ReferenceError: localStorage is not defined. The third version skips it on the server with typeof window !== "undefined", so the build passes. But a visitor who saved Mint tea gets HTML that says Save for later, and the browser's first render says Saved. In its Steps tab, the last step logged Minified React error #418. That's the short form React uses in production for "Hydration failed because the server rendered text didn't match the client. As a result this tree will be regenerated on the client." React threw away that part of the HTML and rendered it again in the browser. The Effect version avoids the problem: its first render matches the HTML, and the Effect changes it afterwards. (Hydration, in the React course, shows what React checks.)
SaveButton reads localStorage in an Effect, so its first render in the browser matches the HTML, and the Effect changes it afterwards. Bottom: it reads it during render, behind typeof window, so the first render doesn't match, and React logs error #418.Keep the boundary low
Every "use client" file pulls its imports into the browser, so the higher in the component tree you put it, the more goes with it. Put it on the small parts that need the browser, the leaves of the tree, and keep the rest on the server. Here is a Green tea page two ways: the whole page as a Client Component, or only its Add to cart button.
app/page.tsxNext.jsapp/page.tsx"use client"; import { useState } from "react";import { teas } from "./data"; export default function GreenTeaPage() { const tea = teas.find((item) => item.id === "green")!; const [added, setAdded] = useState(false); return ( <main> <h1>{tea.name}</h1> <p>{tea.about}</p> <button onClick={() => setAdded(true)}>{added ? "Added" : "Add to cart"}</button> </main> );}Small leaves picked in early spring, steamed the same day.
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
Line 1 makes the page itself a Client Component, so app/data.ts, imported on line 4, goes to the browser too: all three teas' descriptions, though the page shows one.
Both pages look the same in the Browser tab, and both were prerendered as ○ /, with the same content in their HTML. The difference is in the JavaScript the browser downloads, which the page doesn't show. The recording searched each version's JavaScript for three texts. Each row is one text: is it in that version's JavaScript?
| The text | The whole page | Only the button |
|---|---|---|
picked in early spring: Green tea's description, which the page shows | Yes. | No. |
dried lemon peel: Lemon tea's description, which the page never shows | Yes. | No. |
Add to cart: The button's label | Yes. | Yes. |
With the whole page as a Client Component, data.ts went to the browser, with Lemon tea's description that this page never shows. With only the button, neither description is in the JavaScript; the one the page shows arrives only as rendered text. Each row below compares the two versions:
| Question | The whole page is a Client Component | Only the button is |
|---|---|---|
| What goes to the browser as code? | The page, and everything it imports: all of data.ts. | Only AddToCart, and what its file imports. |
Can the page be async and use secrets? | It can't be async, and it must not read secrets: its code goes to the browser, and its server render can put what it reads into the HTML. | Yes. It's still a Server Component. |
| What does React hydrate? | The whole page. | Only the button. |
| What does the visitor see first? | The same page: Next.js renders both on the server. | The same page. |
page.tsx starts with "use client", so data.ts goes too, with all three teas' descriptions. Right: only add-to-cart.tsx does, and the description reaches the browser as one rendered paragraph.Components from packages without the directive
Components from npm packages follow the same rule. A package whose components need the browser should start their files with "use client", and Next.js's own Link does: you render it from Server Components all the time. Some packages don't, often because they were written before Server Components existed.
Here, acme-rating stands in for one: a small package written for this lesson, placed in the project's node_modules folder the way npm installs one. Its index.js uses useState and has no directive. Packages ship plain JavaScript, so it calls createElement instead of using JSX. Try rendering it from the page, then through a wrapper:
app/page.tsximport { Rating } from "acme-rating"; export default function MintTeaPage() { return ( <main> <h1>Mint tea</h1> <Rating label="Your rating" /> </main> );}$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled Creating an optimized production build ...✓ Compiled successfully Running TypeScript ... Finished TypeScript ... Collecting page data ...Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-errorTypeError: (0 , c.useState) is not a function or its return value is not iterable at ignore-listed frames { digest: '3028760187'}Export encountered an error on /page: /, exiting the build.⨯ Next.js build worker exited with code: 1 and signal: nullThe page, a Server Component, renders Rating from the package. The package's file has no directive, so it runs as part of a Server Component, with the build of React made for Server Components, which has no useState. The prerender stops with a TypeError.
Rendered by the page, the package's code runs as part of a Server Component, with the build of React made for Server Components, which has no useState. The prerender stops with TypeError: (0 , c.useState) is not a function or its return value is not iterable. That's less clear than the message for your own files, which Next.js checks as it compiles them; this package's file got no such message. The fix is a wrapper of your own: app/rating.tsx has "use client" on line 1, imports Rating on line 3 and exports it again on line 5. Now your file is the entry point, and the package comes in as one of its imports. In the wrapper version's Steps tab, clicking 2 stars changes the rating to 2 of 3.
Put "use client" at the top of the files whose components need the browser, so people can render them from Server Components without a wrapper. Next.js's docs warn that some bundlers remove directives when they build a package, so check that yours keeps them.
Where does "use client" go?
Put "use client" on the files that a Server Component renders and that need the browser. The files they import don't need it. Sort these files from this lesson:
app/page.tsx, which shows the tea and rendersOrderBox.order-box.tsx, which the page renders and which keepsaddedin state.cup-counter.tsx, which onlyorder-box.tsximports.price.ts, a helper that formats prices.rating.tsx, which wraps a package'sRatingfor the page.save-button.tsx, which the page renders and which readslocalStoragein an Effect.
Sort each file: does it need the directive at its top, or not?
Every card matches a recording in this lesson. The page built without a directive, cup-counter.tsx worked without one while order-box.tsx imported it, and the wrapper and the Save for later button started the client part where the page rendered them.
Common mix-ups
- "A Client Component renders only in the browser." No. It renders on the server first, into the HTML:
CupPicker's message is innext build's output, and its buttons are in the page. - "Every file in the client part needs
"use client"." No. Only the entry points that Server Components render.cup-counter.tsxhad none. - "A file without the directive stays on the server." No. If a client file imports it, it goes to the browser:
Free today, fromprice.ts, was in the page's JavaScript. - "Making the whole page a Client Component costs nothing." No. Everything it imports goes to the browser, like all three teas' descriptions. The page can no longer be
async, and it must not read secrets: its code goes to the browser, and its render on the server can put what it reads into the HTML. - "Every click in a Client Component sends a request." No. A click that only updates local state, like One more, sent none. A handler sends one only when it fetches something or calls a Server Function.
- "A
typeof windowcheck makes browser-only code safe while rendering." No. The server and the browser then render different things, and React logs a hydration error and renders that part again.
Practice exercises
These exercises are about Next.js projects, which can't run inside this page. Answer first, then use the hints.
This is app/cup-counter.tsx, which only order-box.tsx imports. Is its code in the JavaScript the page loads?
import { useState } from "react";
import { formatPrice } from "./price";
export default function CupCounter({ price }: { price: number }) {
const [cups, setCups] = useState(1);
return (
<p>
{cups} {cups === 1 ? "cup" : "cups"}, {formatPrice(cups * price)}{" "}
<button onClick={() => setCups(cups + 1)}>One more</button>
</p>
);
}
Yes. order-box.tsx is an entry point, and it imports cup-counter.tsx, so the file is part of the client. The recording found One more, which is only in this file, in the page's JavaScript.
After hydration, the visitor clicks One more. How many requests does the click send to the server?
"use client";
import { useState } from "react";
export default function CupPicker({ price }: { price: number }) {
const [cups, setCups] = useState(1);
console.log(`CupPicker rendered with ${cups} cup`);
return (
<p>
<button onClick={() => setCups(cups - 1)} disabled={cups === 1}>
One less
</button>{" "}
{cups} {cups === 1 ? "cup" : "cups"}: ${cups * price}{" "}
<button onClick={() => setCups(cups + 1)}>One more</button>
</p>
);
}
None. setCups updates state in the browser, and React renders CupPicker again there. In the recording, both clicks on One more made no request.
The build stops with ReferenceError: localStorage is not defined. Which line causes it?
"use client";
import { useState } from "react";
export default function SaveButton({ id }: { id: string }) {
const [saved, setSaved] = useState(localStorage.getItem("saved-tea") === id);
function save() {
localStorage.setItem("saved-tea", id);
setSaved(true);
}
return (
<button onClick={save} disabled={saved}>
{saved ? "Saved" : "Save for later"}
</button>
);
}
Line 6. It reads localStorage during render, and the build renders Client Components on the server too, where localStorage isn't defined. The recorded build stops at app/save-button.tsx:6:38. Line 9 is fine: it runs only when the button is clicked, in the browser.
A visitor saved Mint tea yesterday and opens the page again. What does the button say in the HTML that next start sends?
"use client";
import { useEffect, useState } from "react";
export default function SaveButton({ id }: { id: string }) {
const [saved, setSaved] = useState(false);
useEffect(() => {
setSaved(localStorage.getItem("saved-tea") === id);
}, [id]);
function save() {
localStorage.setItem("saved-tea", id);
setSaved(true);
}
return (
<button onClick={save} disabled={saved}>
{saved ? "Saved" : "Save for later"}
</button>
);
}
Save for later. The HTML is rendered on the server, where saved starts at false and the Effect never runs. The recorded HTML says Save for later; only after hydration does the Effect change it to Saved.
This file was meant to be a Client Component, but the build fails. Which line is in the wrong place?
import { useState } from "react";
"use client";
export default function CupPicker({ price }: { price: number }) {
const [cups, setCups] = useState(1);
return (
<button onClick={() => setCups(cups + 1)}>
{cups} {cups === 1 ? "cup" : "cups"}: ${cups * price}
</button>
);
}
"use client";
import { useState } from "react";
export default function CupPicker({ price }: { price: number }) {
const [cups, setCups] = useState(1);
console.log(`CupPicker rendered with ${cups} cup`);
return (
<p>
<button onClick={() => setCups(cups - 1)} disabled={cups === 1}>
One less
</button>{" "}
{cups} {cups === 1 ? "cup" : "cups"}: ${cups * price}{" "}
<button onClick={() => setCups(cups + 1)}>One more</button>
</p>
);
}
Line 2, the directive. It must come before every other line of code, imports included. The recorded build stops at ./app/cup-picker.tsx:2:1: The "use client" directive must be placed before other expressions. Without a working directive, the file is a Server Component, so the build also complains about useState.
This page renders Rating from acme-rating, whose file has no directive, and the prerender stops with a TypeError. Write app/rating.tsx, a wrapper the page can render. What is its first line?
import { Rating } from "acme-rating";
export default function MintTeaPage() {
return (
<main>
<h1>Mint tea</h1>
<Rating label="Your rating" />
</main>
);
}
"use client";
import { Rating } from "acme-rating";
export default Rating;
"use client";. Your wrapper is the entry point, so the package's code comes in as one of its imports and runs in the client part. Render Rating from ./rating in the page. The recorded build passes with ○ /, and in real Chrome, clicking 2 stars changes the rating.
Check your understanding
For each question, ask where the boundary is, and where each render happens.
Question 1 of 7What does
"use client"at the top of a file mark?Choose an answer to see the explanation.
Question 2 of 7When does
CupPicker, a Client Component, render on a first visit to the prerendered page?Choose an answer to see the explanation.
Question 3 of 7What does
next builddo with this page, given thecup-counter.tsxfrom the lesson, which has no directive?Read the code, then predictTSXimport CupCounter from "./cup-counter"; import { formatPrice } from "./price"; export default function MintTeaPage() { return ( <main> <h1>Mint tea</h1> <p>{formatPrice(3)} a cup. Fresh mint leaves, dried in the sun.</p> <CupCounter price={3} /> </main> ); }Choose an answer to see the explanation.
Question 4 of 7After hydration, the visitor clicks One more. What does the browser send to the server?
Choose an answer to see the explanation.
Question 5 of 7
SaveButtonreadslocalStoragewhile it renders, behindtypeof window !== "undefined". A visitor who saved Mint tea opens the page again. What happens?Choose an answer to see the explanation.
Question 6 of 7Why put
"use client"on the Add to cart button rather than on the whole tea page?Choose an answer to see the explanation.
Question 7 of 7A package's
RatingusesuseState, but its file has no"use client". How do you render it from a Server Component page?Choose an answer to see the explanation.
Key takeaways
"use client"at the top of a file marks an entry point: that file, and everything it imports, go to the browser, with or without the directive.- Put it on the files that Server Components render, not on every file. A file that a Server Component imports is part of the server.
- Client Components can use state, Effects, event handlers and browser APIs. A state update in the browser sends nothing to the server.
- They still render on the server first, into the HTML, and React hydrates them in the browser. Code that runs during render must work on the server and give the same result there; read browser-only data in an Effect.
- Keep the boundary low: make the small interactive parts Client Components, and keep pages, layouts and data on the server.
- Wrap a package's component that lacks the directive in a file of your own that starts with
"use client".
Remember the one-liner."use client" marks where the browser part starts; it still renders on the server, so keep it small and at the leaves.
These links point at Next.js v16.4.3, the version every example was built with. The message for a directive below an import is on lines 318 to 321 of react_server_components.rs, the code that reads directives while Next.js compiles your files. And Next.js's own Link starts with 'use client' on line 1 of link.tsx. 'use client' and 'use server' under the hood reads how the boundary is built.
Coming next: Composing Server and Client Components. It puts Server Components inside Client Components, and shows which props can cross the boundary.