Skip to content
Back to blog
Production noteFeatured•Sep 24, 2026

Your Next.js client boundary is a product decision

The placement of 'use client' shapes browser JavaScript, data ownership, and how quickly a page becomes useful.

Next.jsReactPerformanceArchitecture

A directive with a surprisingly large radius

In the App Router, "use client" marks a boundary in the module graph. Components imported below that boundary become part of the client graph. That matters when a large page component is marked client-side only because one small control needs an onClick handler.

The question is not "Can this page be a Client Component?" It can. The question is what the browser now has to download and run, and what the server can no longer keep behind the boundary.

Put interactivity where it happens

Imagine a careers page with a job description, requirements, and an apply button. The description is content; the button opens a dialog. Only the interactive part needs client state.

// app/careers/[slug]/page.tsx — Server Component
import { ApplyButton } from './apply-button';
 
export default async function JobPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const job = await getPublicJob(slug);
 
  return (
    <main>
      <h1>{job.title}</h1>
      <p>{job.description}</p>
      <ApplyButton jobId={job.id} />
    </main>
  );
}
// app/careers/[slug]/apply-button.tsx — Client Component
'use client';
 
import { useState } from 'react';
 
export function ApplyButton({ jobId }: { jobId: string }) {
  const [open, setOpen] = useState(false);
  return <button onClick={() => setOpen(true)}>Apply for {jobId}</button>;
}

The button example is intentionally minimal. A real dialog would need accessible focus handling, form validation, and a server write. The boundary still stays near the interaction.

A server boundary is also a data boundary

The server can fetch directly from the database and use secrets without sending those capabilities to the browser. The Client Component should receive only the data it needs, such as a public job ID and label. Avoid passing a large record because one field is needed inside a button.

This also keeps authorization in the right place. A hidden admin button is not access control. The server operation behind it must verify the caller, regardless of how the UI is rendered.

The Next.js Server and Client Components guide documents both the boundary behavior and the intended split of responsibilities.

Watch for the accidental client cascade

The common warning sign is a file at the top of a route containing "use client" plus imports for everything on the page: header, article body, cards, tables, and controls. The next engineer adds a date formatter or chart package, and the client graph quietly grows.

A useful refactor is to move the interactive piece into a leaf component, then pass it the smallest serializable props. Composition can still let a Client Component render server-provided children without importing their implementation into the client graph.

Measure the outcome, not the aesthetic

This boundary is not a purity contest. Some screens genuinely need rich client interaction. For those, a larger client graph may be worth it.

What matters is whether the choice matches the page's job:

  • A public article should become readable quickly.
  • An admin editor should respond quickly to input.
  • A product page may need both: server-rendered facts and a small interactive purchase control.

The best boundary is the one that delivers the right experience with the least unnecessary browser work.

If you are building a web product and want it faster, cleaner, and easier to operate, I am open to collaborations.