skip to content

In a Next.js App Router app, a team adds 'use client' to the top of app/dashboard/layout.tsx. Which code actually crosses into the client boundary as a result, and do the pages rendered inside that layout become Client Components too?

level: seniorimportance: should knowfreq 42%

answer

  1. two trees, only one inherits
  2. imports inherit, route nesting does not
  3. children arrive already rendered
  4. layout loses async, headers, metadata
  5. shared shell is the import-heavy part

basics

~20 s

The boundary follows the import graph, not the route tree. The layout module and everything it imports become client code; the pages nested under it do not, because Next renders them separately and passes their output in as the children prop.

solid answer

~40 s

Two different trees are in play and only one of them inherits the directive. `'use client'` propagates along imports: the layout module becomes a Client Component and every module it imports — nav, sidebar, icon set, whatever — joins the client graph. Route nesting is not an import relationship: `app/dashboard/page.tsx` is not imported by the layout, Next renders it separately and hands the rendered output in as `children`, so it stays a Server Component. What the layout itself loses is significant, though: it can no longer be an `async` component awaiting data, it cannot import `next/headers`, and it cannot export `metadata` or `generateMetadata`. The fix is almost always to leave the layout on the server and mark only the interactive shell it renders.

code

tsx · 18 lines
tsx
// app/dashboard/layout.tsx — stays a Server Component
import { Tabs } from './tabs' // 'use client' lives in tabs.tsx

export const metadata = { title: 'Dashboard' }

export default async function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const user = await getUser()
  return (
    <section>
      <Tabs userName={user.name} />
      {children}
    </section>
  )
}

go deeper

for a junior

Know that the directive is inherited through imports, so the layout and the files it imports become client code, while the pages rendered inside it arrive through children and stay on the server.

for a middle

Explain the mechanism behind that split — Next renders each route segment separately and passes the child's output in as a prop — and list what the layout itself loses: async data access, next/headers, metadata exports.

for a senior

Show the production judgment: quantify why a layout is the worst place for the directive because of its import subtree and its persistence across navigations, then restructure by marking only the interactive shell.

for a principal

Own the review standard. Be ready to say why a one-line directive on a shared layout is a change worth gating, and how you keep boundary placement visible in a codebase where the cost never shows up in the diff.

## Two trees, only one of which inherits An App Router app has an *import graph* and a *route tree*, and they are easy to conflate because both look like nesting. The import graph is what `'use client'` travels down. A module carrying the directive is an entry point into the client graph, and everything it imports — and everything those modules import — is compiled for the browser. The route tree is a filesystem convention. `app/dashboard/page.tsx` sitting inside `app/dashboard/` means its output is rendered *inside* the layout, but the layout does not import it. Next renders each segment and passes the child segment's already-rendered output to the parent as its `children` prop. So the answer splits cleanly: everything the layout imports crosses; nothing the layout merely *contains* crosses. ## What concretely changes - The layout module itself is now a Client Component. - Every module in its import subtree joins the client graph and ships to the browser. - Pages and nested layouts below it in the route tree keep rendering on the server; their output arrives through `children`. - The layout can no longer be declared `async` and await data in its body. - It can no longer import from `next/headers` — `cookies`, `headers`, `draftMode` are server-only. - It can no longer export `metadata` or `generateMetadata`; those are supported in Server Components only. That last group is usually what surfaces the mistake: the build fails on the metadata export or on a server-only import, and the directive is the cause even though it looks unrelated. ## Why "it ships the whole app" is half true The claim is inaccurate as stated — pages under the layout genuinely stay on the server. But it is directionally right about bundle size, because a real layout is one of the most import-heavy files in an app. Navigation, sidebar, breadcrumb, theme provider, analytics wrapper, an icon library: a layout usually imports the shared shell of the entire section, and that whole subtree crosses at once. And because a layout persists across navigations within its segment, everything it pulled in is present on every route beneath it. ## Fixing it Keep the layout a Server Component and push the directive down to the interactive piece: ```tsx // app/dashboard/layout.tsx — no directive import { Tabs } from './tabs' // 'use client' lives in tabs.tsx export const metadata = { title: 'Dashboard' } export default async function DashboardLayout({ children, }: { children: React.ReactNode }) { const user = await getUser() return ( <section> <Tabs userName={user.name} /> {children} </section> ) } ``` The layout keeps its `async` body, its `metadata` export, and its server-side data access; only `tabs.tsx` and its imports cross. Nothing about the visual result changes. ## Diagnosing it in an existing codebase When a layout has already gone client, work backwards from what forced it. Something in that file wanted state, a handler or a browser API — find it, and check whether it is one small piece (extract it) or the layout is doing genuinely interactive work throughout (rare, and usually a sign the layout is holding logic that belongs in a component). If the directive was added to silence an error from an imported dependency, the fix is a wrapper module around that dependency rather than a directive on the layout. ## Reviewing for it `'use client'` appearing at the top of a `layout.tsx` in a pull request deserves a question every time: what needed it, and can that thing live in its own module instead? It is one of the few one-line diffs that can move a large share of an app's shared shell into the browser bundle, and the diff itself gives no hint of the size of the change — the cost lives in the layout's import list, not in the line that was added. ## The sentence to land in an interview Directives are inherited through imports, not through route nesting; a client layout still receives server-rendered `children`; and what the layout personally gives up — async data access, `next/headers`, metadata exports — is usually the real reason not to do it.

  • If the pages really do stay server-rendered, why do people warn that a client layout ships the whole app?
    Because of what a layout imports rather than what it contains. Navigation, sidebar, providers and icon sets are usually all imported by the layout, so one directive moves the entire shared shell of that section into the browser — and it is present on every route beneath it.
  • What usually breaks first when a layout becomes a Client Component?
    The build, on something that looks unrelated: a `metadata` or `generateMetadata` export, an `async` layout body, or an import from `next/headers`. All three are Server-Component-only, so the error names them rather than the directive that actually caused it.
  • Can a client layout inspect or transform the children it receives?
    Not meaningfully. The child segment was rendered on the server and arrives as an already-produced element to place in the tree. The layout decides where it goes and what wraps it, not what it contains — anything the child needs has to come from the child's own segment.

Imports are a family tree: the directive is inherited by every descendant. Route nesting is a seating arrangement — the page sits inside the layout, but it is not descended from it, so it inherits nothing.

saying these in an interview costs you the question

  • Says every page under a client layout becomes client too
  • Thinks the directive propagates down the route tree
  • Believes the children prop must be client code as well
  • Adds the directive to a layout to fix one interactive widget
  • Cannot name anything the layout gives up in exchange

context