Last updated: 2026-10-08

Server Components and Client Components

The distinction between Server Components and Client Components is the most important concept in the Next.js App Router. Everything else — how data is fetched, how rendering works, how performance is achieved — follows from this distinction. Understanding it clearly, rather than treating it as a vague background detail, is what separates developers who work with the App Router confidently from those who work around it cautiously.

The core idea is deceptively simple: some components run on the server, some run in the browser, and the two environments have fundamentally different capabilities. Next.js makes this explicit, gives developers control over where each component runs, and builds its entire rendering model around it.


Two Environments, Two Component Types

A web application runs code in two places. The server is where requests are received, data is fetched, and responses are prepared. It has access to databases, file systems, environment variables, and the full Node.js runtime. The client is the browser — where JavaScript runs after it has been downloaded, where the DOM lives, where user interactions happen.

In the traditional React model, every component runs in the browser. Components are downloaded as JavaScript, executed in the browser, and produce DOM output there. The server's role was limited: it either served the raw HTML shell and let the browser do everything else, or it ran a special server rendering step to produce initial HTML, but the component code still shipped to the browser for hydration.

The App Router changes this. Components are now server-first. The browser receives what the server computed, not the instructions to compute it.


Server Components

A Server Component is a React component that runs exclusively on the server. It is never sent to the browser as JavaScript. It renders on the server, produces its output — HTML, or React's serialised component tree — and that output is what travels to the client.

Every component in the App Router is a Server Component by default. No annotation, no import, no configuration required. A file like this is a Server Component:

// app/blog/page.tsx
export default async function BlogPage() {
  const posts = await fetch('https://api.example.com/posts').then(r => r.json());

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

There is nothing here that marks this as a Server Component — its absence of 'use client' is what makes it one. It runs on the server, fetches data directly, renders the list, and the browser receives fully populated HTML. No JavaScript for this component is downloaded by the browser. No hydration happens for it. It simply does not exist in the browser's world.

What Server Components Can Do

Server Components have capabilities that are unavailable to any client-side code:

Direct data access. A Server Component can query a database, call internal APIs, read files, or fetch external data — all directly, without going through an API endpoint. The data never makes a round trip through the client. It is fetched on the server and rendered into HTML before the browser sees anything.

// Directly querying a database in a component — impossible in the browser
export default async function UserProfile({ id }: { id: string }) {
  const user = await db.user.findUnique({ where: { id } });
  return <h1>{user.name}</h1>;
}

Access to server-only resources. Environment variables without the NEXT_PUBLIC_ prefix, secret API keys, internal configuration — all accessible directly because the component never leaves the server environment.

Reduced JavaScript sent to the browser. Every Server Component is one less piece of JavaScript the browser has to download, parse, and execute. For components that are purely presentational — rendering data that was fetched, displaying content that does not change in response to user interaction — there is no reason to send them to the browser at all.

No performance cost for heavy dependencies. A Server Component can import large libraries — a markdown parser, a syntax highlighter, a date formatting library — without those libraries ever being included in the browser's JavaScript bundle, because the component itself never runs in the browser.

import { marked } from 'marked'; // this library stays on the server

export default async function MarkdownPost({ slug }: { slug: string }) {
  const raw = await fs.readFile(`content/${slug}.md`, 'utf8');
  const html = marked(raw);

  return <article dangerouslySetInnerHTML={{ __html: html }} />;
}

What Server Components Cannot Do

The server environment imposes real constraints. Server Components have no access to anything that belongs to the browser:

  • No React hooks — useState, useEffect, useContext, useRef, and all other hooks require the browser runtime. They cannot be used in Server Components.
  • No browser APIs — window, document, localStorage, navigator, and any other browser global are unavailable.
  • No event handlers — onClick, onChange, onSubmit cannot be attached in a Server Component, because event listeners only make sense in a browser.
  • No interactivity — anything that responds to user input, manages state, or updates in reaction to an event requires the client.

These are not limitations of the framework — they are the nature of the server environment. Code that runs before the browser exists cannot know what the browser will do.


Client Components

A Client Component is a React component that runs in the browser. It is the React model that developers have always known — components with state, effects, event handlers, and access to the browser environment. In the App Router, Client Components are opted into explicitly with the 'use client' directive at the top of the file.

'use client';

import { useState } from 'react';

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

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

The 'use client' directive is a boundary declaration — it tells Next.js that this component and everything it imports belongs to the client. It is not a per-component annotation but a module-level directive: once a file declares 'use client', all components in that file, and all modules imported from that file, become part of the client bundle.

What 'use client' Actually Means

'use client' does not mean the component only runs in the browser. It means the component runs in both environments during the initial render.

On the initial page load, Next.js server-renders Client Components too — they produce HTML on the server that reaches the browser immediately, just like Server Components. The difference is that the browser then downloads the JavaScript for Client Components and hydrates them — attaching event listeners and activating the React runtime so the component becomes fully interactive. Server Components receive no such treatment. Their JavaScript is never in the browser, so there is nothing to hydrate.

After the initial load, when the user navigates or interacts with the page, Client Components re-render entirely in the browser, as standard React components always have.

What Client Components Can Do

Client Components have full access to the browser environment and the complete React API:

  • All React hooks — useState, useEffect, useContext, useReducer, useRef, and others
  • Browser APIs — window, document, localStorage, navigator
  • Event handlers — onClick, onChange, onSubmit, and all other event listeners
  • Third-party libraries that depend on the browser environment

The Boundary Between Server and Client

Understanding how Server and Client Components interact requires understanding where the boundary between them sits and what rules govern crossing it.

Server Components Can Import and Render Client Components

A Server Component can import a Client Component and render it as part of its output. This is the normal, expected pattern — a page that is mostly a Server Component for data fetching, with a few interactive Client Components embedded within it.

// app/dashboard/page.tsx — Server Component
import { SearchBar } from '@/components/SearchBar'; // Client Component
import { UserList } from '@/components/UserList';   // Server Component

export default async function DashboardPage() {
  const users = await db.user.findMany();

  return (
    <div>
      <SearchBar />       {/* Client Component — interactive */}
      <UserList users={users} />  {/* Server Component — presentational */}
    </div>
  );
}

Client Components Cannot Import Server Components

The reverse does not work. A Client Component cannot import a Server Component directly, because a Server Component cannot run in the browser — and once a module is part of the client bundle, everything it imports must also be able to run in the browser.

'use client';

// This does not work — cannot import a Server Component into a Client Component
import { DatabaseTable } from '@/components/DatabaseTable'; // Server Component

This is not a limitation that will be lifted — it is a fundamental consequence of the two environments being different. A Server Component has access to server-only resources. If it ran in the browser, those resources would not be available.

Passing Server Components as Props to Client Components

The correct pattern for composing Server and Client Components when nesting is needed is to pass Server Components as the children prop — or any other prop — to a Client Component, rather than importing them.

// app/dashboard/page.tsx — Server Component
import { Modal } from '@/components/Modal';       // Client Component
import { UserDetails } from '@/components/UserDetails'; // Server Component

export default async function DashboardPage() {
  const user = await db.user.findFirst();

  return (
    <Modal>
      <UserDetails user={user} />
    </Modal>
  );
}
// components/Modal.tsx — Client Component
'use client';

import { useState } from 'react';

export function Modal({ children }: { children: React.ReactNode }) {
  const [isOpen, setIsOpen] = useState(true);

  return isOpen ? (
    <div>
      {children}
      <button onClick={() => setIsOpen(false)}>Close</button>
    </div>
  ) : null;
}

UserDetails is a Server Component passed as children to the Client Component Modal. The Server Component renders on the server. The Client Component renders on both server and client, receiving the already-rendered Server Component output as its children prop. The two coexist without either violating the boundary.


Practical Decision Making — Which to Use

The decision of whether to write a Server or Client Component is not a performance optimisation to apply case by case — it is a default to follow and an exception to reach for when necessary.

The default is a Server Component. The exception is a Client Component, reached for specifically when one of these conditions is true:

Reach for a Client Component when...Example
The component needs React stateA controlled input, a toggle, a counter
The component uses React hooksuseState, useEffect, useContext
The component responds to user eventsButton clicks, form submissions, keyboard input
The component uses browser APIslocalStorage, window.innerWidth, the clipboard
The component uses a third-party library that requires the browserMany animation and UI component libraries

If none of these conditions apply — the component fetches data, displays it, and does nothing interactive — it should be a Server Component.

The practical consequence of following this default is meaningful. A typical content-heavy page — a blog post, a product detail page, a documentation article — is almost entirely Server Components. The data is fetched, the HTML is rendered, and the browser receives a fully formed page with a minimal JavaScript footprint. Client Components appear only at the edges: a share button, a comment form, a collapsible section. The result is a faster page that loads less JavaScript, with interactivity precisely where it is needed.


A Mental Model for the Boundary

One way to internalise the Server/Client distinction is to think of it in terms of time. Server Components exist at request time — they run when a user asks for a page, they have access to the server's resources at that moment, and their output is fixed once sent. Client Components exist at interaction time — they run in the browser, they respond to what the user does, and they can change after the initial render.

A component that presents information from a database is a Server Component — the database exists at request time, on the server. A component that lets the user filter, sort, or search that information is a Client Component — filtering responds to user interaction, which happens in the browser after the initial render.

This framing does not cover every edge case, but it resolves the decision clearly for the vast majority of components a developer will write.


Summary

Server ComponentClient Component
DefaultYes — no annotation requiredNo — requires 'use client'
Runs onServer onlyServer (initial render) + Browser
JavaScript sent to browserNoneYes — hydrated in the browser
React hooksNot availableAvailable
Event handlersNot availableAvailable
Browser APIsNot availableAvailable
Direct data accessYes — database, file system, env varsNo — must call an API
Can import the otherYes — can import Client ComponentsNo — cannot import Server Components
Composition patternPass Client Components as childrenReceive Server Component output as children