Server Components and Client Components
Understand the most important architectural concept in modern Next.js: deciding which components execute on the server and which require browser capabilities.
Server Components Are the Default
In the App Router, components are Server Components by default. This means their rendering logic can execute on the server and their implementation does not automatically become part of the browser's JavaScript bundle.
Server Components are excellent for data-heavy UI, database access, sensitive operations, and static content.
A Server Component can directly call a server-side service. This is one of the biggest architectural differences between modern Next.js and a traditional client-only React application.
- Server Components are the default in the App Router.
- Server Components can access server-only resources.
- Keeping computation on the server can reduce browser JavaScript.
The use client Directive
A component becomes a Client Component when its module begins with the use client directive.
Client Components are appropriate when code needs browser APIs, event handlers, local state, effects, or client-side libraries that depend on the DOM.
The directive defines a boundary. Everything imported into that client boundary must be compatible with client execution. This means use client should not be added casually to large parent components.
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}- Use
use clientonly when browser-side interactivity is required. - Client boundaries should be as small as practical.
- Do not turn an entire application into Client Components unnecessarily.
Passing Data Across the Boundary
Server Components can render Client Components and pass serializable props to them. A common pattern is to fetch data on the server and provide only the data required for interactivity to a client component.
For example, a product page can retrieve product information on the server while an AddToCartButton remains a Client Component.
This architecture allows the server to perform data access while the browser handles interaction.
import AddToCartButton from './AddToCartButton';
export default async function ProductPage() {
const product = await getProduct();
return (
<section>
<h1>{product.name}</h1>
<p>{product.price}</p>
<AddToCartButton productId={product.id} />
</section>
);
}- Server Components can render Client Components.
- Pass only the data needed by the client.
- Keep database and secret-dependent logic on the server.
Protecting Server-Only Modules
A large application can accidentally import server-only functionality into client code. This can expose secrets or create build errors.
The server-only package can be used to explicitly mark modules that must never be imported into client code.
This is particularly useful for database clients, authentication utilities, payment providers, and internal service clients.
The important question is not simply 'does this component render?' but 'where should this code execute?'
import 'server-only';
export async function getPrivateUserData(userId: string) {
// Database access or private API access
return database.users.findById(userId);
}- Use explicit server-only boundaries for sensitive modules.
- Database clients should never become browser dependencies.
- Server/client boundaries are architectural decisions, not just syntax.
Composition Patterns for Server and Client Components
One common mistake is moving an entire page into a Client Component because one small widget needs interactivity. Instead, compose a mostly server-rendered page around small client islands.
For example, a dashboard can contain server-rendered statistics, a server-rendered table, and a small client-side filter component.
This pattern reduces JavaScript while preserving rich interaction.
- Prefer small interactive islands instead of clientifying entire pages.
- Compose Server and Client Components rather than choosing one globally.
- The best boundary often occurs around the smallest interactive feature.