Next.js Server vs Client Boundary Explained: Where `use client` Actually Starts

Every Next.js component tree is Server Components by default. Learn exactly where the client boundary begins, what it pulls with it, and how to still render a Server Component past that line.

7 min read

The Next.js server client boundary is the line where a component tree stops being Server Components by default and starts shipping code to the browser. That line begins at the first file that starts with the "use client" directive, and every component that file imports or renders directly becomes part of the client bundle from that point down.

This matters because the boundary is not drawn around one component, it is drawn around a whole module and everything that module pulls in. Reaching for the directive in the wrong file can quietly turn a large section of your tree into client code.

App.tsxApp.tsx
// app/[id]/page.tsx
import LikeButton from '@/app/ui/like-button'
import { getPost } from '@/lib/data'
 
type Params = Promise<{ id: string }>
 
export default async function Page({ params }: { params: Params }) {
  const { id } = await params
  const post = await getPost(id)
  return <LikeButton likes={post.likes} />
}

The page component stays a Server Component. It runs only on the server, so it fetches the post data directly and never ships that data-fetching code to the browser.

App.tsxApp.tsx
// app/ui/like-button.tsx
'use client'
 
import { useState } from 'react'
 
export default function LikeButton({ likes }: { likes: number }) {
  const [count, setCount] = useState(likes)
  return <button onClick={() => setCount(count + 1)}>{count} likes</button>
}

The like button runs on the server first to produce HTML, then again in the browser during hydration, because that file is where the boundary starts. Clicking the button updates its count entirely in the browser after that.

Why the boundary is a module boundary, not a component boundary

The "use client" directive marks the boundary on the module dependency tree, not on the render tree. That means the rule applies to a whole file, not to one exported function inside it, so a single file can export several components at once.

Every one of them becomes a Client Component once the directive is present, whether or not that specific component uses state, an effect, or a browser API.

App.tsxApp.tsx
// app/ui/panel.tsx
'use client'
 
export function PanelHeader({ title }: { title: string }) {
  return <h2>{title}</h2>
}
 
export function PanelBody({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>
}

Both exported components in this file are Client Components now, even though the header component never touches state or an event handler. Putting the directive on a shared file forces every export in that file across the boundary at once.

Avoid adding the directive to a large shared utility file just to fix one interactive piece. Every neighbor in that file follows it across the same line, adding weight to the client bundle for components that never needed it.

Use client does not turn off server rendering

A common misconception is that the directive stops a component from being server rendered. It does not. Next.js still renders a Client Component to HTML on the server during the initial request, and the browser hydrates that HTML afterward instead of mounting from an empty shell.

texttext
Direct visit to /posts/1:
1. Server renders the like button to HTML (server)
2. Browser shows that HTML immediately
3. React hydrates the like button in the browser (client)
4. Click handlers become active after hydration

On a client-side navigation later, the server only sends the RSC Payload for that route, so a Client Component further down the tree can render entirely in the browser without a fresh server-rendered HTML pass. The word "Client" describes where a component's code ships, not where it is allowed to render.

The diagram below shows the same idea across a tree. Everything below the marked file joins the client module graph, except a Server Component passed in as a prop.

Server and client boundary through a component tree

The modal component starts the boundary. The close button is imported inside the modal's own module, so it becomes client code too. The cart component is a Server Component that the page passes into the modal as children, so it keeps rendering on the server even though it visually sits inside the client subtree.

Getting a Server Component past the boundary

You cannot import a Server Component from inside a Client Component. That import would pull server-only code, such as a database call, into the client bundle, so the build fails instead of silently working.

The composition pattern is the only way back. A Server Component above the boundary renders the Server Component itself, then hands the finished output to the Client Component through children or another prop.

App.tsxApp.tsx
// app/page.tsx
import { Cart } from '@/app/ui/cart'
import { Modal } from '@/app/ui/modal'
 
export default function Page() {
  return (
    <Modal>
      <Cart />
    </Modal>
  )
}

The page and cart components are Server Components, so the cart finishes rendering on the server. The modal receives that result as serialized data through its children, never as source code, which is why the cart stays out of the client bundle even though it renders inside the modal.

App.tsxApp.tsx
// app/ui/modal.tsx
'use client'
 
import { useState } from 'react'
 
export function Modal({ children }: { children: React.ReactNode }) {
  const [open, setOpen] = useState(true)
  return open ? (
    <div role="dialog">
      <button onClick={() => setOpen(false)}>Close</button>
      {children}
    </div>
  ) : null
}

The modal never imports the cart, so the modal's module graph has no idea the cart exists. It only treats its children prop as an opaque slot to place inside its markup, which is what lets a Server Component keep its server-only code while a Client Component controls whether that content shows at all.

A practical use case

Reach for this pattern when a mostly static page needs one interactive wrapper, such as a modal, an accordion, or a tab panel, around content that still needs server data. A product page is a common example: the product details and reviews come from a database and belong in Server Components, while the add-to-cart button and its confirmation panel need client state to open and close.

Keep the wrapper's own file as the only Client Component in that section, and pass every data-heavy piece into it as a prop instead of importing it there. This keeps the bulk of the page's JavaScript weight down to just the interactive shell, not the content inside it.

Common confusion

Developers often add the directive to a layout or a shared UI file just to fix one interactive child, which drags every sibling in that file into the client bundle. Move the directive down to the smallest file that actually needs state, an event handler, or a browser API, and pass Server Components into it through props instead. This is the difference between a client subtree that stays small and one that quietly swallows most of the page.

For the full list of when each component type is the right choice, see Server Components vs Client Components in Next.js. For a deeper look at the directive itself, see The use client Directive Explained. For more on passing data the other direction, see How to Pass Props Across the Server Client Boundary, and for more composition patterns, see Passing Server Components as Children to Client Components.

Rune AI

Rune AI

Key Insights

  • A Next.js component tree is Server Components by default until a file adds use client.
  • use client marks a module boundary, so every component that file exports becomes a Client Component.
  • Client Components still render to HTML on the server first, then hydrate in the browser.
  • Everything a Client Component imports and renders directly joins the client bundle.
  • A Server Component passed as children or a prop keeps rendering on the server, because the Client Component only receives its output, not its code.
  • You cannot import a Server Component from inside a Client Component; composition through props is the only way back.
RunePowered by Rune AI

Frequently Asked Questions

Does adding use client turn off server rendering for that component?

No. Next.js still renders a Client Component to HTML on the server first, then hydrates it in the browser. The directive changes whether the component's code ships to the client, not whether the server renders it.

Can one file with use client export both a client and a server component?

No. The directive marks the whole module, so every component that file exports becomes a Client Component, even ones that never use state or browser APIs.

Once inside a Client Component, can I import a Server Component directly?

No. A Client Component cannot import a Server Component's module, because that import would pull server-only code into the client bundle. The only way to render a Server Component past the boundary is to have a Server Component above the boundary pass it down as children or another prop.

Does passing a Server Component as a prop send its code to the browser?

No. The Server Component still renders on the server. The Client Component only receives its already-rendered output as serialized data, never its source code.

Conclusion

The server and client boundary in Next.js starts at the first file marked with use client, and it is a module boundary, not a per-component one. Everything that file imports and renders directly joins the client bundle, but a Server Component passed in as children or another prop keeps rendering on the server because its code was never imported into that module.