Most Next.js developers know the usual rule: add 'use client' when a component
needs state, event handlers, effects, or browser APIs.
That rule is correct, but incomplete.
'use client' does not describe only the file containing the directive. It
declares an entry point into the client module graph. The modules reachable
through its static imports can also become part of the JavaScript prepared for
the browser.
A correctly placed directive can still create a broad boundary
Consider an interactive wishlist panel:
'use client'
import ProductList from '../_shared/ProductList'
export default function ClientPanel() {
const [open, setOpen] = useState(true)
return (
<section>
<button onClick={() => setOpen(!open)}>Toggle</button>
{open && <ProductList />}
</section>
)
}Placing 'use client' on ClientPanel is reasonable because the panel owns
state and an event handler. The less obvious consequence is its import graph:
ClientPanel → ProductList → priceUtilsProductList may contain only static presentation, and priceUtils may use no
browser API at all. They are still reachable from the client entry point.
The effective boundary is therefore not the physical size of ClientPanel. It
is the transitive module graph reachable from it.
Change ownership with server composition
Suppose the panel needs to control visibility but does not need to own the product implementation. A Server Component can import the list and pass its rendered result into a client-owned slot:
// page.tsx — Server Component
import ProductList from '../_shared/ProductList'
import ClientPanel from './ClientPanel'
export default function Page() {
return (
<ClientPanel>
<ProductList />
</ClientPanel>
)
}// ClientPanel.tsx
'use client'
export default function ClientPanel({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(true)
return (
<section>
<button onClick={() => setOpen(!open)}>Toggle</button>
{open && children}
</section>
)
}The panel still owns its interaction, but it no longer imports ProductList.
The list remains visually inside the panel while its implementation stays
server-owned.
Visual parenthood is not module ownership.
In the talk's controlled production build, the import-expansion route produced a 2,028-byte raw minified route-specific client artifact, while the composition route produced a 1,107-byte artifact. The 921-byte difference is not a universal performance result. It is evidence that changing ownership changed the build output.
If the implementation stayed on the server, what crossed?
The browser still displays the product list, but it does not need the
ProductList implementation for this composition.
On the server, React executes the Server Component and represents its rendered
result in the React Server Component payload. That representation fills the
children slot alongside references and props needed to connect the Server and
Client Component trees.
The ProductList function, a DOM node, and raw HTML are not passed as an ordinary
children value. On the initial load, server-generated HTML provides the visible
preview, the RSC payload helps React reconcile the tree, and client JavaScript
hydrates the interactive panel.
Functions require a different transport mechanism
Ordinary functions cannot be serialized like strings or objects. Their code and captured memory belong to one process.
A Server Function uses a special mechanism:
async function purchaseKolkata() {
'use server'
return { confirmation: 'SERVER-REFUSED-42' }
}Its implementation remains on the server. The client receives a special reference containing an action ID and dispatcher. Invoking it sends a POST with the action ID and supported arguments. Next.js resolves that ID, executes the implementation on the server, and returns a serialized result.
The action ID is not authentication or a permanent API name. Server Functions must still authenticate, authorize, validate input, and constrain returned data.
Review the boundary in three steps
When reviewing 'use client', ask:
- Import: Which modules become reachable from this client entry point?
- Transport: What value, rendered result, module reference, or Server Function reference must cross between runtimes?
- Hydrate: Which region genuinely needs state, events, effects, or browser APIs?
The goal is not always the physically smallest component. Shared state, accessibility, coordination, and maintainability can justify a larger boundary. The goal is the smallest useful client boundary.
Put the boundary around the interaction, not around everything the interaction can display.

