Next.jsReactServer ComponentsApp Router

Next.js Server vs Client Components: How to Choose

Choose between Server Components and Client Components in the Next.js App Router by looking at data access, state, events, browser APIs, serialization, and bundle size.

·Updated ·10 min read·Counting...
Next.js Server vs Client Components: How to Choose

In the Next.js App Router, pages and layouts are Server Components by default. Add "use client" only when a component needs state, event handlers, effects, browser APIs, or client-side context. The goal is not to make everything server-only. The goal is to keep the client boundary as small and intentional as possible.

Quick decision table

NeedServer ComponentClient Component
Read a database or server secret directlyYesNo
Fetch public content on the serverYesSometimes, but often unnecessary
Use useState or useReducerNoYes
Use onClick or onChangeNoYes
Use useEffect or windowNoYes
Reduce JavaScript sent to the browserYesNo

Start server-side, then move only interactive leaf components to the client.

Default Server Components

CODE
// app/blog/page.tsx
export default async function BlogPage() {
  const posts = await getPosts()

  return (
    <main>
      <h1>Blog</h1>
      <PostList posts={posts} />
    </main>
  )
}

A Server Component can read server-side data without shipping its implementation as interactive JavaScript. It cannot use event handlers, state, effects, or browser globals.

Keep "use client" narrow

If only a search box is interactive, mark only the search box:

CODE
// components/SearchBox.tsx
"use client"

import { useState } from "react"

export function SearchBox() {
  const [query, setQuery] = useState("")
  return <input value={query} onChange={(event) => setQuery(event.target.value)} />
}

The page can remain a Server Component:

CODE
import { SearchBox } from "@/components/SearchBox"

export default async function BlogPage() {
  const posts = await getPosts()
  return (
    <>
      <SearchBox />
      <PostList posts={posts} />
    </>
  )
}

"use client" defines a module graph boundary. The file and the dependencies it imports become part of the client module graph. Avoid placing it at the top of a large page or root layout just to support one button.

Server UI can be passed into Client UI

Client Components can receive already-rendered server UI through children:

CODE
// components/Modal.tsx
"use client"

export function Modal({ children }: { children: React.ReactNode }) {
  return <dialog>{children}</dialog>
}
CODE
<Modal>
  <ServerRenderedArticle />
</Modal>

Do not import server-only modules directly into a Client Component file. Compose them from a Server Component parent instead.

Props must be serializable

Data crossing from a Server Component to a Client Component must be serializable. Plain objects, arrays, strings, numbers, and booleans are usually fine. Database connections, class instances, arbitrary functions, and complex runtime objects are not.

Dates should be converted deliberately, usually to strings, then formatted with clear timezone behavior. Otherwise the server and browser may render different initial output.

What happens on first load

Next.js renders Server Components on the server and produces the React Server Component payload. The browser receives HTML for the initial view, then hydrates Client Components with JavaScript so they become interactive.

This means Client Components may still be pre-rendered into initial HTML. "Client Component" does not mean "only rendered in the browser." If the server render and the first browser render differ, you can still get a hydration mismatch. For debugging, see React Hydration Failed: Causes, Debugging, and Fixes.

Common mistakes

  • Adding "use client" to an entire page for a single interactive control.
  • Reading window in a Server Component.
  • Passing private environment variables into browser code.
  • Passing non-serializable objects across the server-client boundary.
  • Refetching first-screen data in useEffect when the server could fetch it directly.
  • Using typeof window to render different initial JSX.
CODE
Server Page
├── Server Header
├── Server ArticleList
│   └── Client FavoriteButton
└── Client SearchPanel
    └── Initial data from the server

Move client boundaries up only when multiple interactive components truly need shared client state.

Subscribe to FreeMac

Weekly picks: free Mac software reviews, trusted source updates, alternatives, and low-friction guides.