'use client'

The 'use client' Directive and the Client Boundary

'use client' goes on the first line of a file, above the imports. It does not mark one component; it marks an entry point into the client module graph. Everything that file imports, transitively, is compiled into the client bundle, and every component in that subtree behaves the way React 7,897 components behaved before RSC. You never need the directive below the entry — only on the file where the boundary starts.

Measure what that costs. Two routes render the same Markdown with marked 18.0.13; one parses on the server, the other needs a live preview, so its parse has to happen on every keystroke, in the browser.

The same dependency on each side of the boundaryJavaScript
// app/notes/page.js — a Server Component
import { marked } from "marked";
const source = "# Release notes\n\nNext.js **16.3.5** ships Turbopack.\n";
export default function Notes() {
  return <article dangerouslySetInnerHTML={{ __html: marked.parse(source) }} />;
}
// app/notes-client/preview.js — a Client Component
"use client";
import { marked } from "marked";
import { useState } from "react";
export default function Preview({ source }) {
  const [text, setText] = useState(source);
  return <>
    <textarea value={text} onChange={(e) => setText(e.target.value)} />
    <article dangerouslySetInnerHTML={{ __html: marked.parse(text) }} />
  </>;
}

Both routes produce byte-identical <article> markup. They do not cost the same. Adding up every <script src="/_next/static/..."> the production HTML loads, measured on disk after next build: /notes loads six scripts totalling 552.5 kB raw and 169.0 kB gzipped, /notes-client seven totalling 595.2 kB and 181.8 kB. One directive added 42.7 kB of raw JavaScript, 12.8 kB over the wire, in a route-specific chunk whose first bytes are unmistakably marked's tokenizer. The shared 552 kB is the framework and React, cached across every route, so the delta is the number to watch.

The directive splits one folder into two module graphs
The directive splits one folder into two module graphs

The fix for a bloated bundle is rarely to delete interactivity; it is to move the directive closer to the leaf that needs it. A layout with a logo, navigation links and a search box does not become a Client Component because of the search box: mark Search, and leave the layout on the server. Two corollaries: React context does not exist in Server Components, so a provider must be a Client Component taking children, rendered as deep in the tree as its consumers allow; and a third-party component calling useState without the directive needs a wrapper of your own — a three-line module that reads "use client";, imports it and re-exports it.