Skip to main content
Next.js learning series · Blog 5

Next.js 16 Server and Client Components Explained

Learn where App Router components run, when browser interactivity creates a client boundary, and how to keep data secure and client JavaScript focused.

A secure server and database connected through a component boundary to an interactive browser interface

You now know how to create pages, layouts, and navigation in the Next.js App Router. The next major concept is understanding where component code belongs. Some components can stay on the server; others need browser-side state, events, or APIs.

This guide builds a practical server-first mental model, explains 'use client', and shows how to compose both component types without sending unnecessary code or private data to the browser.

Server vs Client Components at a Glance

Start with the capability a component needs. Server Components are suited to server data access, secrets, data transformation, and UI that does not require browser interaction. Client Components are suited to state, event handlers, effects, custom hooks, and browser APIs.

Diagram 1: Server Components vs Client ComponentsA React component can remain on the server for data and rendered UI or enter the client module graph when it needs browser interaction. Server output, Client Component references, and serializable props are coordinated through the React Server Component payload.

This is a composition model, not a choice between two separate applications. On an initial request, Next.js can use Server and Client Components to pre-render HTML. In the browser, JavaScript hydrates the Client Components so their event handlers work. Later navigations use the RSC payload to update the tree.

What Is a Server Component?

A Server Component is rendered in the server environment and is the default in the App Router. It does not need a directive. A simple page with static content is already a Server Component:

app/about/page.tsx
export default function AboutPage() {
  return (
    <main>
      <h1>About</h1>
      <p>This component does not need browser interactivity.</p>
    </main>
  )
}

Server Components can call server-side data functions close to the data source. They can also keep API credentials and private implementation details out of the client bundle. That does not make every server value safe to expose: explicitly select the fields the interface needs. The next tutorial explains Next.js data fetching with APIs, databases, and streaming in detail.

Server-side data example
type Post = { id: string; title: string }

async function getPosts(): Promise<Post[]> {
  // Tutorial data. Replace this with your real server data source.
  return [
    { id: '1', title: 'Server Components' },
    { id: '2', title: 'Client Components' },
  ]
}

export default async function BlogPage() {
  const posts = await getPosts()

  return (
    <main>
      <h1>Blog</h1>
      {posts.map((post) => (
        <article key={post.id}><h2>{post.title}</h2></article>
      ))}
    </main>
  )
}

What Is a Client Component?

A Client Component is part of the client module graph and can use React state, events, effects, and browser APIs. Add 'use client' at the top of its entry file, before imports:

app/ui/counter.tsx
'use client'

import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)

  return (
    <button type="button" onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  )
}

The browser is required because a user click triggers onClick, which updates retained state through setCount and causes React to update the button.

Diagram 2: Why a counter needs a Client ComponentA browser click reaches an event handler, updates React state, and refreshes the visible count. Static article content has no event or local state and can remain a Server Component.

Server Components Are the Default

Files such as app/page.tsx, app/layout.tsx, and app/blog/page.tsx are Server Components unless a client boundary changes the relevant module graph. This matches the route model explained in our layouts and pages guide.

Default App Router files
app/
|-- layout.tsx       # Server Component
|-- page.tsx         # Server Component
`-- blog/
    `-- page.tsx     # Server Component

There is no 'use server' directive for declaring a component as a Server Component. That directive has a different purpose: it marks Server Functions callable across the network boundary.

Understanding 'use client'

The directive defines an entry point into the client module graph. Once a module is marked, its imports and transitive dependencies are treated as client code when reached through that boundary. You do not need to repeat the directive in every imported interactive child.

A large server-rendered component tree with one small violet interactive client boundary
Keep the boundary focused. Most content can stay in the server tree while one compact interactive module enters the client graph.
Diagram 3: The use client boundaryA Server Component tree reaches ClientBoundary.tsx. The directive makes that module and the modules it imports part of the client graph, while sibling server content stays outside.

When to Use Each Component Type

RequirementPreferWhy
Read from a database or private APIServer ComponentKeeps access and secrets on the server
Render static article or product detailsServer ComponentNo browser state is required
useState, useEffect, custom hooksClient ComponentRequires a client lifecycle
onClick or onChangeClient ComponentBrowser event handlers provide interaction
window, document, localStorageClient ComponentThose APIs exist in the browser
Navigation hooks such as useRouterClient ComponentHooks react to browser-side navigation state

Client Components are not a failure or a fallback. They are the correct place for interaction. The goal is to choose a boundary that includes the interaction without pulling unrelated server-capable content into the client bundle.

State, Events, and Browser APIs

A Server Component cannot retain browser state or register an onClick handler. It also cannot read window, document, or localStorage because those objects do not exist in the server environment. Move only the part that needs those capabilities into a Client Component.

Navigation hooks follow the same rule. The useRouter example from Blog #4 is a Client Component because it responds to a click. A normal Link does not require your surrounding file to become a Client Component.

Keep Data and Secrets on the Server

Server Components can call private services without bundling credentials into browser JavaScript. Keep environment variables, database clients, privileged API tokens, and authorization logic in server-only modules. Prefixing an environment variable with NEXT_PUBLIC_ intentionally makes it available to client code, so never use that prefix for a secret.

Diagram 4: Keep secrets on the serverThe Server Component uses private credentials to request data, filters the result, and sends only safe serializable fields to a Client Component. The browser never receives the secret.
  1. SecretServer environment
  2. Private requestAuthorized data source
  3. Filter resultSelect safe fields
  4. Serializable propsMinimal public data
  5. Client UIBrowser interaction
Guard the module boundary

Add import 'server-only' to modules that must never enter the client graph. Next.js then reports a build-time error if a Client Component imports them. This complements—not replaces—careful data selection and authorization.

Pass Server Data to a Client Component

A Server Component can fetch or calculate data and pass the small interactive component only what it needs:

app/products/[id]/page.tsx
import { LikeButton } from './like-button'

export default async function ProductPage() {
  const product = await getProduct()

  return (
    <main>
      <h1>{product.name}</h1>
      <LikeButton productId={product.id} />
    </main>
  )
}
app/products/[id]/like-button.tsx
'use client'

export function LikeButton({ productId }: { productId: string }) {
  return <button type="button">Like product {productId}</button>
}

Props crossing from server to client must be serializable by React. Strings, numbers, booleans, arrays, and plain data objects are common safe choices. Do not pass a database connection, class instance, arbitrary function, request object, or secret-bearing record. Server Functions are a special supported function type, but they still require validation and authorization when called.

Can Server and Client Components Be Mixed?

Yes. A Server Component can import and render a Client Component normally. A product page might keep its heading, details, description, and reviews on the server while a small add-to-cart control handles browser interaction.

Diagram 5: Server-first component treeThe Product Page, Header, Product Details, and Reviews remain Server Components. Add to Cart and its Quantity Selector form a focused Client Component subtree.

Keep the interactive boundary small. Only the controls that retain state or handle events need browser JavaScript.

The reverse direction needs composition rather than a direct import. A Client Component should not import an arbitrary Server Component and expect it to stay server-only. Instead, a parent Server Component renders the server content and passes that content into a Client Component through children or another slot:

Client wrapper and Server parent
// app/ui/modal.tsx
'use client'

export function Modal({ children }: { children: React.ReactNode }) {
  return <div className="modal">{children}</div>
}

// app/page.tsx — Server Component
import { Modal } from './ui/modal'
import { Cart } from './ui/cart'

export default function Page() {
  return <Modal><Cart /></Modal>
}

Cart is rendered on the server ahead of time, while Modal controls client-side visibility. The slot lets server-rendered UI appear visually inside interactive client UI without importing the server module into the client graph.

Why the Client Boundary Matters

Client modules contribute JavaScript that the browser downloads and evaluates. Their interactive output must also hydrate so event handlers attach. A boundary that wraps a large mostly static page can increase bundle size, hydration work, and mental overhead.

Avoid an unnecessarily broad boundary
'use client'

export default function EntireArticlePage() {
  // A large mostly static article does not need to be client code.
}

A better component tree keeps ArticlePage and ArticleContent on the server, then renders a focused CopyButton Client Component. Do not invent a performance percentage; inspect the actual bundle and user experience.

Context Providers and Third-Party Components

React context is not supported directly in Server Components, so place the provider implementation in a Client Component and render it from a Server Component. Wrap only the subtree that needs the value. Keeping the provider as deep as practical gives Next.js more static server structure to optimize.

app/theme-provider.tsx
'use client'

import { createContext } from 'react'

export const ThemeContext = createContext('light')

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  return <ThemeContext.Provider value="light">{children}</ThemeContext.Provider>
}

Third-party components that use state, effects, or browser APIs also require a client boundary. A library that already declares its client entry can be imported directly. If it does not, create a small wrapper marked 'use client'. Do not assume every package needs this treatment.

Build a Server-First Product Card

A product card assembled from server-rendered modules with a small interactive favorite control
Server-first product UI. Product information stays in server-rendered modules while one favorite control provides browser interaction.
app/ui/product-card.tsx
import { FavoriteButton } from './favorite-button'

const tutorialProduct = {
  id: 'headphones-1',
  name: 'Studio Headphones',
  price: '$129',
}

export function ProductCard() {
  return (
    <article>
      <div aria-hidden="true">Product image placeholder</div>
      <h2>{tutorialProduct.name}</h2>
      <p>{tutorialProduct.price}</p>
      <FavoriteButton productId={tutorialProduct.id} />
    </article>
  )
}
app/ui/favorite-button.tsx
'use client'

import { useState } from 'react'

export function FavoriteButton({ productId }: { productId: string }) {
  const [saved, setSaved] = useState(false)

  return (
    <button
      type="button"
      aria-pressed={saved}
      onClick={() => setSaved(!saved)}
    >
      {saved ? 'Saved' : `Save ${productId}`}
    </button>
  )
}

The mock object is clearly tutorial data. The card structure and text need no state, so they remain on the server. Only the favorite button enters the client graph.

Common Server and Client Component Mistakes

  • Adding 'use client' everywhere. This pulls imported modules into the client graph and weakens the server-first boundary.
  • Calling useState in a Server Component. Move the state and controls into a Client Component.
  • Reading browser APIs during server rendering. Access window, document, and localStorage from appropriate client logic.
  • Exposing secrets through props. Filter data and pass only safe fields needed by the interface.
  • Making an entire layout client-side for one control. Keep the layout on the server and import a small interactive child.
  • Importing server-only code into client code. Use clear module boundaries and server-only guards.
  • Thinking the directive affects only one component function. It defines a module boundary that includes its imported dependency graph.
  • Thinking Client Components only participate in browser rendering. Next.js can pre-render them into initial HTML on the server, then hydrate them in the browser.

Server and Client Component Best Practices

  • Start with Server Components and add a client boundary where interaction requires it.
  • Keep secrets, database clients, and privileged APIs server-side.
  • Pass minimal serializable data into Client Components.
  • Keep interactive modules focused and avoid converting whole pages or layouts.
  • Use server composition and children when server content must appear inside client UI.
  • Place context providers as deep as the consuming subtree allows.
  • Check whether third-party packages already expose an appropriate client entry.
  • Keep component files discoverable using the conventions from the Next.js project structure guide.
  • Verify framework behavior against current official documentation as React and Next.js evolve.

Frequently Asked Questions

Are Next.js components Server Components by default?

Yes. In the App Router, layouts and pages are Server Components by default unless a client boundary brings a module into the client graph.

What does use client mean in Next.js?

The use client directive marks a module as a client entry point. Its imports and transitive dependencies become part of the client module graph.

When should I use a Client Component?

Use one for state, event handlers, effects, custom hooks, or browser APIs such as localStorage and window.

Can I use useState in a Server Component?

No. Server Components do not retain browser state or attach event handlers. Move stateful UI into a focused Client Component.

Can a Server Component import a Client Component?

Yes. A Server Component can render an imported Client Component and pass it serializable props.

Can a Client Component import a Server Component?

Do not import a Server Component into the client module graph. Instead, a Server Component can pass already server-rendered content to a Client Component through a prop such as children.

Can Server Components access a database?

They can call server-side data-access code, subject to your runtime and security architecture. Return only the fields the rendered interface needs.

Are Client Components bad for performance?

No. They are the correct tool for interactivity. Performance problems arise when client boundaries include much more JavaScript and hydration work than the interface requires.

Official Resources

Continue with the official Next.js Server and Client Components documentation and the use client reference. React's client directive reference provides the detailed serialization model.

Next Steps

You can now keep pages server-first, add focused interaction, pass safe serializable props, and compose server-rendered content through client wrappers. Continue by learning to fetch data in the right part of the component tree.

WhatsApp