Skip to content
Zayne Lovecraft
All posts

Architecture · · 1 min read · Updated

Client Components and the "use client" Boundary in Next.js

How the "use client" directive defines the boundary between server and client module graphs, and how to compose components so heavy trees stay on the server.

Zayne Lovecraft

Software Engineer III

Focused view of a computer screen displaying programming code with visible reflections.
Photo by Daniil Komov on Pexels

"use client" marks a module boundary

In the App Router, every component is a Server Component by default. Adding "use client" at the top of a file marks where the bundler crosses into the client bundle. Everything that file imports becomes client code too, unless it's passed in as children or props from a Server Component.

Keep heavy trees on the server

A common mistake is putting "use client" at the top of a page just to add a toggle or modal. Instead, wrap the stateful part around children so data fetching and static markup stay server-rendered.

"use client";
import { useState, type ReactNode } from "react";

export function InteractiveShell({ children }: { children: ReactNode }) {
  const [open, setOpen] = useState(false);
  return (
    <div data-open={open}>
      <button onClick={() => setOpen((v) => !v)}>Toggle</button>
      {children}
    </div>
  );
}

Passing functions across the boundary

Plain functions can't be serialized from a Server Component to a Client Component. When a client component needs to mutate server state, use a Server Action ("use server"). Next.js turns it into a callable endpoint for you. Treat it like a public API route and validate its inputs and auth.

  • Next.js
  • React
  • Server Components

Keep reading