WritingNext.js Server Components vs Client Components: When to Use Each — LinkIT
5 min readnext.js, server-components, app-router, react

Next.js Server Components vs Client Components: When to Use Each

A practical breakdown of Next.js server components vs client components in the App Router — what each does, when to reach for it, and the mistakes to avoid.

In the App Router, the placement of use client affects what enters the browser bundle. Keep that boundary close to interactive code so server-side data access and non-interactive rendering can remain on the server.

The examples below cover the responsibilities of each component type and how to compose them.

What Server Components Do

Server components render on the server and send the result to the client as HTML (and a lightweight React tree for reconciliation). They never ship their own JavaScript to the browser.

Because they live on the server, they can:

  • Query a database directly using an ORM
  • Read environment variables and secrets without exposing them
  • Import server-only Node.js modules
  • Access the filesystem

They cannot:

  • Use browser APIs (window, document, localStorage)
  • Use React state (useState) or lifecycle hooks (useEffect)
  • Register event handlers (onClick, onSubmit)
  • Use context that depends on client state

In the App Router, every component is a server component by default. You opt into the client only when you have a specific reason to.

What Client Components Do

Client components are the React you've always written. They hydrate in the browser and can respond to user events, manage local state, and read browser APIs.

Adding use client at the top of a file marks the entire module — and everything it imports — as part of the client bundle. That directive does not make a component "dynamic" in the Next.js sense. It just tells the bundler to ship that code to the browser.

The Decision Rule

Ask one question: does this component need to react to user input or read browser state?

If yes — reach for use client. If no — leave it as a server component.

Common client component candidates:

  • Interactive forms with controlled inputs
  • Dropdown menus, modals, tabs driven by local state
  • Components that call useEffect to subscribe to events
  • Anything using third-party libraries that depend on window

Everything else — layouts, data-fetching wrappers, static text blocks, navigation shells — should stay on the server.

How to Compose Them Correctly

Push the boundary down

Putting use client on a high-level module pulls its imported dependencies into the client module graph. Server-rendered content can still be passed into a Client Component as children; component nesting alone does not determine the boundary.

Instead, isolate interactivity into small leaf components. A product page can be a server component that fetches data, renders the product description, and drops in a single client component AddToCartButton at the leaf.

Pass Server-Rendered Content as Children

You can pass server component output into a client component as children. A pattern that works well:

// page.tsx (server component)
import { InteractiveShell } from './interactive-shell'
import { getProduct } from '@/lib/db'

export default async function ProductPage({ params }) {
  const product = await getProduct(params.id)
  return (
    <InteractiveShell>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </InteractiveShell>
  )
}

InteractiveShell is a client component but its children — the product data rendered as HTML — came from the server and are never re-fetched on the client.

Never import server-only code from a client component

If you import a module that uses fs or a database driver inside a use client file, Next.js will either error at build time or silently strip the code. Use the server-only package to make the boundary explicit and catch violations early.

// lib/db.ts
import 'server-only'
export async function getProduct(id: string) { ... }

The use server Confusion

use server at the top of a function marks a Server Action — a callable server-side function exposed as a POST endpoint. It does not make a component a server component. Components are server components by default unless marked otherwise. Actions are the mechanism for mutations from client components.

Confusing these two directives is one of the most disorienting early mistakes teams make.

Performance Implications

Server components reduce bundle size because their code never ships to the client. A complex data transformation done in a server component costs nothing in JavaScript weight. The same logic in a client component adds to the initial JS payload and runs again in the browser.

On the initial load, Next.js prerenders HTML using both Server and Client Components. Client Component JavaScript then hydrates the interactive parts. Server rendering and data fetching still determine how quickly the response can begin. Next.js component guide.

The practical advice: default to server, reach for use client only when the component has a clear interactive job.

A Quick Reference

CapabilityServer ComponentClient Component
Database / ORM accessYesNo
Secret env varsYesNo
useState / useEffectNoYes
Event handlersNoYes
Browser APIsNoYes
Streaming / SuspenseYesYes

Review component boundaries when bundle size grows or server-only dependencies reach client code.

Discuss Next.js component architecture with LinkIT.