In the Next.js App Router, files under app/ render on the server unless told otherwise. Which of your components actually need a 'use client' directive, and which do not?
answer
- default is server, opt in explicitly
- ask what forces the opt-in
- state, event handlers, browser globals
- markup and data fetching need nothing
- mark the smallest module, not the page
basics
~20 sOnly components that need browser behaviour: React state or effect hooks, DOM event handler props such as onClick, or browser APIs like window and localStorage. Everything else — markup, layout, data fetching — stays a Server Component with no directive.
solid answer
~40 sIn the App Router everything under `app/` is a Server Component by default, so the real question is what forces you out of that default. Three things do: hooks that need a live component instance (`useState`, `useEffect`, `useRef`, `useContext`), DOM event handler props like `onClick` or `onChange`, and browser-only APIs such as `window`, `localStorage` or `IntersectionObserver`. A fourth case is indirect — a dependency that does one of those internally without shipping its own directive. If a component only receives props, maps data to JSX, or awaits a query, it needs nothing. And because the directive applies to the module it sits in *and* everything that module imports, the practical skill is placing it on the smallest interactive file rather than on the page or layout above it.
code
tsx · 12 lines'use client'
import { useState } from 'react'
export function LikeButton({ initial }: { initial: number }) {
const [count, setCount] = useState(initial)
return (
<button type="button" onClick={() => setCount(count + 1)}>
{count} likes
</button>
)
}go deeper
Be able to say that App Router files are server-side by default and name the three triggers out loud: state or effect hooks, event handler props like onClick, and browser APIs such as window or localStorage.
Explain why those three are the triggers — a server render happens once with no instance to hold state and no way to serialize a function — and why static, prop-driven components are fine on the server.
Show the placement judgment: mark the smallest interactive module, keep pages and layouts on the server, and describe how you would diagnose a component that quietly ended up on the client.
Own the direction of the default. Be ready to argue why the loud failure (server code touching the browser) is cheaper than the quiet one (silently widening the client boundary), and how you keep a team's habits pointed that way.
## The default you are opting out of In the App Router, every file under `app/` — pages, layouts, and the components they import — renders on the server unless some module in its import chain declares `'use client'`. That default is Next's decision, not React's: the Pages Router took the opposite position, where everything under `pages/` was client code that happened to be prerendered to HTML. So in App Router work the interesting question is never "how do I make this render on the server", it is "what forced this one to be client?" ## The three things that force the directive **1. Hooks that need a living component instance.** `useState`, `useReducer`, `useEffect`, `useLayoutEffect`, `useRef`, `useContext`, and any custom hook built from them. A Server Component renders once and is finished; there is no instance to hold state in or to re-run an effect against. `createContext` belongs in the same bucket — you cannot create or provide a context from a Server Component. **2. DOM event handler props.** `onClick`, `onChange`, `onSubmit`, `onMouseEnter` — anything that passes a function into a DOM element prop. The server render produces a serialized description of the UI, and a function has no representation in it, so Next fails the render with an error saying event handlers cannot be passed to Client Component props. **3. Browser-only APIs.** `window`, `document`, `localStorage`, `navigator`, `IntersectionObserver`, `addEventListener` — none of them exist in the Node process doing the render. There is a fourth, indirect case: a dependency that does any of the above internally. If the package ships its own `'use client'`, you can import it straight from a Server Component and the boundary begins inside the package; if it does not, you have to open the boundary yourself. ## What does not force it A surprising amount of an app. Mapping an array to JSX, conditional rendering, receiving props and rendering markup, importing a pure formatting helper, awaiting a database query or a `fetch`, reading `cookies()` or `headers()` from `next/headers` — that last one is the reverse case, since `next/headers` is server-only and importing it into a client module is an error. Presentation components are the common surprise: a card, a table row, a badge, a nav list built from `<Link>` — none of them need the directive. ## Placement matters more than the decision `'use client'` is written as the first statement of a module, above the imports, and it applies to that module *and* everything the module imports, transitively. That is why "which component needs it?" is really "which module do I write it in?" The directive on a small `like-button.tsx` costs you that button; the same directive on the page above it pulls every import of that page into the client graph. ```tsx // app/products/page.tsx — no directive, renders on the server import { LikeButton } from './like-button' // that file has 'use client' export default async function Page() { const items = await getProducts() return ( <ul> {items.map((item) => ( <li key={item.id}> {item.name} <LikeButton initial={item.likes} /> </li> ))} </ul> ) } ``` ## Telling where a component actually ran In development, a Server Component's `console.log` appears only in the terminal running the dev server. A Client Component's appears in the browser devtools — and usually in the terminal too, because Next prerenders client components to HTML for the first paint. A log showing up in both places is a reliable tell that the module landed inside the client boundary. Deleting the directive and reading the error Next raises tells you exactly what forced it. ## The cost of being wrong in each direction Wrong toward the client is quiet: the page still works, it just ships more JavaScript and any server-side data access in that module has to move somewhere else. Wrong toward the server is loud: the render fails immediately with a message naming the hook or the handler. That asymmetry is the argument for keeping the default and opting out narrowly — the failure mode that shouts fixes itself, and the one that whispers is the one that accumulates.
- A colleague says every component that ends up visible in the browser needs 'use client'. What is wrong with that?It confuses rendering with running. A Server Component's output is visible in the browser as HTML; the directive is about whether the component's own code ships and executes there. Static markup, lists, cards and data-fetching components are visible and still need nothing.
- How would you check, without reading the code, whether a component ended up on the client?Put a `console.log` in it and run the dev server. A Server Component logs only in the terminal. A Client Component logs in the browser devtools, and typically in the terminal too because Next prerenders it for the first paint. Seeing it in both places is the tell.
- A Server Component needs a Save button. Can it render any interactivity at all?It can render the `<button>` markup, but it cannot attach an `onClick`. The normal move is to extract just the button into its own small client module and render that from the server component, keeping the surrounding page on the server.
saying these in an interview costs you the question
- Says every component needs 'use client' to appear in the browser
- Adds the directive to the page to fix one button
- Thinks 'use client' is required to receive props
- Believes Server Components mean the page ships zero JavaScript
- Cannot name a concrete thing that forces the directive