HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
5 Topics
Chapter•110 Minutes

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 Component Execution
Key Takeaways
  • 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.

Interactive Client Component
'use client';

import { useState } from 'react';

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

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}
💡 useState and event handlers require browser-side React behavior, so the component must be a Client Component.
Key Takeaways
  • Use use client only 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.

Server to Client Data Flow
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>
  );
}
💡 The server retrieves the product while the interactive button remains isolated as a Client Component.
Key Takeaways
  • 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.

Think in Boundaries

The important question is not simply 'does this component render?' but 'where should this code execute?'

Server-Only Service
import 'server-only';

export async function getPrivateUserData(userId: string) {
  // Database access or private API access
  return database.users.findById(userId);
}
💡 Marking the module as server-only creates an explicit architectural boundary.
Key Takeaways
  • 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.

Server-First Component Composition
Key Takeaways
  • 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.