Lesson 9 of 2226 min read

Server Components vs Client Components in Next.js Explained

Understand the 'use client' directive, React Server Component boundaries, and exactly when to reach for Server vs Client Components in Next.js.

Author: CodersNexus

Server Components vs Client Components in Next.js Explained

Every component you write in the Next.js App Router is, by default, a Server Component — a fact that surprises many developers coming from older React or the Pages Router, where every component rendered in the browser. This default is not an implementation detail; it's arguably the single biggest architectural shift the App Router introduced, and understanding it deeply changes how you structure entire applications.

Server Components render exclusively on the server and send only the resulting HTML (plus a lightweight description of the UI) to the browser — none of their code, and none of their dependencies, are ever shipped as JavaScript to the client. Client Components, marked explicitly with a 'use client' directive, work the way React has always worked: they render (or at least hydrate) in the browser and can use interactive features like state and event handlers.

The skill this lesson builds is knowing where to draw the line between the two — pushing as much as possible to the server for performance, while carving out precise, minimal Client Component boundaries exactly where interactivity is genuinely required.

Learning Objectives

  • Explain what a Server Component is and why it's the App Router's default.
  • Understand what the 'use client' directive does and where it must be placed.
  • Identify which React features require a Client Component.
  • Understand how a 'use client' boundary affects an entire component subtree.
  • Apply a practical decision process for choosing Server vs Client Components.

Core Definitions

  • Server Component: A component that renders exclusively on the server; its code and dependencies are never sent to the browser as JavaScript.
  • Client Component: A component marked with 'use client' that renders (or hydrates) in the browser and can use interactive React features like state and effects.
  • 'use client' directive: A string literal placed at the very top of a file that marks that file, and everything it imports into that render tree, as part of the client bundle.
  • Component boundary: The point in a component tree where rendering responsibility shifts from server to client, established by the 'use client' directive.
  • Server-only code: Code that can safely use server resources — databases, file systems, secret API keys — because it never reaches the browser.
  • Zero bundle size: A property of Server Components where their code contributes nothing to the JavaScript bundle downloaded by the browser.

Detailed Explanation

In the Next.js App Router, any file inside app/ is a Server Component unless you explicitly opt out. This isn't a small default — it's a complete reversal from how React applications traditionally worked, where everything eventually ran in the browser. Server Components run once, on the server, produce their output, and that's it. They never re-render in the browser, never carry their own JavaScript to the client, and can directly access server-only resources: reading a database, calling a private API with a secret key, or reading a file from disk, all without exposing any of that logic or those credentials to users.

But Server Components have a hard limitation: they cannot use React's interactive features. No useState, no useEffect, no onClick handlers, no browser-only APIs like localStorage — none of it, because Server Components never run in a live, interactive JavaScript environment in the browser. If a component needs to remember whether a dropdown is open, respond to a button click, or subscribe to a browser event, it needs to be a Client Component.

You opt into a Client Component by adding the exact string 'use client' as the very first line of a file, before any imports. This single directive does something important: it doesn't just make that one file a Client Component — it establishes a boundary. Everything that file imports and renders (unless those imports themselves establish their own separate boundary) becomes part of the client-rendered subtree, bundled and shipped to the browser as JavaScript.

This is why the most effective pattern is to keep 'use client' boundaries as small and as deep in your component tree as possible — often called 'leaf' components. Rather than marking an entire page 'use client' because one button needs an onClick handler, you extract just that button into its own small Client Component and import it into an otherwise server-rendered page. The page keeps its server-only benefits — direct data fetching, zero JS bundle contribution, access to secrets — while only the truly interactive piece ships JavaScript to the browser.

A useful mental model: Server Components answer 'what does this look like?' using data, often fetched directly with async/await. Client Components answer 'how does this respond to the user?' using state and event handlers. Most real pages are a Server Component shell (fetching data, rendering static structure) wrapping a handful of small, focused Client Components exactly where interactivity is needed.

Diagram Description

Visualize a component tree with a 'use client' boundary:

[app/products/page.tsx] (Server Component — fetches product data)
└── [ProductList] (Server Component — maps over products)
└── [ProductCard] (Server Component — displays name, price)
└── [AddToCartButton] ('use client' — HAS onClick + useState)

Everything above AddToCartButton runs only on the server, sends zero JS to the browser for its own logic. Only AddToCartButton (and anything it imports) is bundled and hydrated client-side.

Comparison Table

AspectServer ComponentClient Component
Where it rendersServer onlyBrowser (hydrates after server-rendered HTML arrives)
Default in App Router?YesNo — requires 'use client'
Can use useState/useEffect?NoYes
Can access secrets/DB directly?YesNo — would expose them to the browser
Contributes to JS bundle size?NoYes
Can use onClick/onChange handlers?NoYes
Typical use caseData fetching, static structure, layoutForms, toggles, dropdowns, real-time UI

Next.js Practical Example

// app/products/page.tsx — Server Component (default, no directive needed)
import AddToCartButton from '@/components/AddToCartButton';

async function getProducts() {
  const res = await fetch('https://api.example.com/products'); // safe: runs on server
  return res.json();
}

export default async function ProductsPage() {
  const products = await getProducts();
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>
          {p.name} — ₹{p.price}
          <AddToCartButton productId={p.id} />
        </li>
      ))}
    </ul>
  );
}

// components/AddToCartButton.tsx — Client Component (needs interactivity)
'use client';

import { useState } from 'react';

export default function AddToCartButton({ productId }: { productId: string }) {
  const [added, setAdded] = useState(false);

  return (
    <button onClick={() => setAdded(true)}>
      {added ? 'Added ✓' : 'Add to Cart'}
    </button>
  );
}

ProductsPage is a Server Component by default — no 'use client' directive is present, and it uses async/await directly inside the component to fetch data, something only possible on the server. It imports AddToCartButton and renders it once per product, but ProductsPage itself never ships to the browser as JavaScript; only the fully rendered HTML for the product list does. AddToCartButton, marked with 'use client', is the only piece of this page that becomes part of the client-side JavaScript bundle — it needs useState to track whether the item was added and an onClick handler to respond to the user, both of which require a Client Component boundary.

Industry Examples

  • E-commerce product pages render product data, descriptions, and images as Server Components for speed and SEO, while isolating the interactive 'Add to Cart' button and quantity selector as small Client Components.
  • News and content sites render entire articles as Server Components (fast, SEO-friendly, zero extra JS) while marking only the comment section or a 'like' button as Client Components.
  • SaaS dashboards fetch and render tabular data as a Server Component, while an inline sort/filter control that needs to respond instantly to clicks is built as a separate Client Component.
  • Analytics platforms use Server Components to fetch and pre-render chart data server-side for speed, while the interactive chart widget itself (hover tooltips, zoom) is a Client Component wrapping a charting library.
  • Documentation sites render markdown-based content as Server Components, isolating only interactive elements like a copy-to-clipboard code block button as Client Components.

Interview Questions and Answers

Q1. What is a Server Component and why is it the default in the Next.js App Router?

A Server Component renders exclusively on the server and never ships its own code to the browser as JavaScript, reducing bundle size and allowing direct, safe access to server-only resources like databases or secret API keys. It's the App Router's default because most UI doesn't need client-side interactivity, and rendering it on the server improves performance and simplifies data fetching.

Q2. What does the 'use client' directive actually do?

'use client' is a string literal placed at the top of a file that marks it as a Client Component, meaning it will be bundled as JavaScript and hydrated in the browser, gaining access to interactive React features like useState and event handlers. It also establishes a boundary — everything that file imports into its render tree becomes part of that client-rendered subtree unless those imports establish their own separate boundary.

Q3. Why can't Server Components use hooks like useState?

Server Components render once on the server and produce static output; they never run in a live, interactive JavaScript environment where state changes could trigger a re-render. Hooks like useState and useEffect depend entirely on this kind of ongoing, interactive execution, which only exists in Client Components running in the browser.

Q4. What is the recommended strategy for structuring Server and Client Components together?

Keep 'use client' boundaries as small and as close to the 'leaves' of the component tree as possible. Rather than marking an entire page as a Client Component because one small part needs interactivity, extract just that interactive piece into its own small Client Component, keeping the surrounding page a Server Component to preserve its performance benefits.

Q5. Can a Server Component import and render a Client Component?

Yes, and this is the standard, expected pattern — a Server Component can import a Client Component and render it directly. The reverse (a Client Component directly importing and rendering a Server Component as a child) is not supported in the same way, though Server Components can be passed into Client Components as children/props.

MCQs With Answers

1. What is the default component type for files inside the Next.js App Router's app/ directory?

  1. Client Component
  2. Server Component
  3. Static Component
  4. Hybrid Component

Answer: B. Server Component

Explanation: Unless explicitly marked with 'use client', every component in the App Router is a Server Component by default.

2. Where must the 'use client' directive be placed in a file?

  1. Anywhere in the file
  2. As the very first line, before any imports
  3. Inside the component function only
  4. In a separate config file

Answer: B. As the very first line, before any imports

Explanation: 'use client' must be the first line of the file, before any import statements, to correctly establish the Client Component boundary.

3. Which of the following requires a Client Component?

  1. Fetching data with async/await
  2. Reading a file from the server's disk
  3. Using the useState hook
  4. Rendering static text

Answer: C. Using the useState hook

Explanation: useState and other interactive hooks depend on a live, browser-based JavaScript environment, which only Client Components provide.

4. What is a key performance benefit of Server Components?

  1. They run faster on old browsers
  2. They contribute zero JavaScript to the client bundle
  3. They automatically cache all data forever
  4. They replace the need for a database

Answer: B. They contribute zero JavaScript to the client bundle

Explanation: Server Components render entirely on the server and never ship their own code as JavaScript to the browser, reducing overall bundle size.

5. What is the recommended practice for placing 'use client' boundaries?

  1. Mark the entire app as a Client Component to be safe
  2. Keep boundaries as small and as deep in the tree as possible
  3. Never use Client Components
  4. Only use them in layout.tsx files

Answer: B. Keep boundaries as small and as deep in the tree as possible

Explanation: The best practice is isolating interactivity into small, focused Client Components, keeping the rest of the tree as Server Components for maximum performance benefit.

Common Mistakes to Avoid

  • Marking an entire page 'use client' just because one small part needs interactivity, unnecessarily bloating the client JavaScript bundle.
  • Trying to use useState, useEffect, or onClick inside a file without the 'use client' directive and being confused by the resulting error.
  • Assuming 'use client' only affects the current file, when it actually establishes a boundary affecting the imported render subtree.
  • Attempting to directly access server-only resources (databases, secret keys) inside a Client Component, which would expose them to the browser.
  • Forgetting that 'use client' must be the literal first line of the file, placing it after imports or comments and having it silently fail to apply correctly.

Interview Notes

  • Server Components are the App Router's default; no directive is needed to use them.
  • 'use client' must be the first line of a file, before any imports, to mark it as a Client Component.
  • Server Components cannot use useState, useEffect, or event handlers like onClick; these require Client Components.
  • Server Components never contribute to the client-side JavaScript bundle, improving performance and reducing load times.
  • Best practice: keep Client Component boundaries small and isolated to the specific interactive pieces of a UI.

Key Takeaways

  • Server Components being the App Router's default is the single biggest architectural shift compared to earlier React and Next.js approaches.
  • The 'use client' directive is a boundary marker, not just a per-component flag — it affects everything rendered beneath it in that subtree.
  • The strongest performance strategy is minimizing Client Component surface area, isolating interactivity into small, focused components.
  • Most real Next.js pages are a blend: a Server Component shell handling data and structure, wrapping small Client Components exactly where interactivity is required.

Summary

The Next.js App Router defaults every component to a Server Component, which renders exclusively on the server, contributes zero JavaScript to the client bundle, and can safely access server-only resources like databases and secret keys. Interactive features — useState, useEffect, onClick handlers, browser APIs — require explicitly opting into a Client Component using the 'use client' directive as the very first line of a file. This directive establishes a boundary: everything rendered beneath it in that subtree becomes part of the client-side JavaScript bundle. The recommended strategy is keeping these boundaries small and deep in the component tree, isolating interactivity into focused Client Components while keeping the surrounding structure as Server Components for maximum performance.

Frequently Asked Questions

A Server Component renders exclusively on the server and ships no JavaScript for itself to the browser, while a Client Component renders/hydrates in the browser and can use interactive React features like state and event handlers, at the cost of contributing to the JavaScript bundle.

No. Server Components are the default in the App Router with no directive required. 'use server' is a different, unrelated directive used for Server Actions (functions callable from the client that run on the server), not for marking a component.

No. useState and other interactive hooks require a live, browser-based rendering environment, which Server Components do not have. You'll need to mark the file with 'use client' to use useState.

Not exactly — Client Components are still typically rendered to HTML on the server for the initial page load (for fast first paint and SEO), then 'hydrated' in the browser to become interactive. What 'use client' really controls is that the component's JavaScript is included in the browser bundle and it can use client-side features.

Yes, generally. Everything that file imports and renders as part of its own JSX becomes part of that client-rendered subtree, unless one of those imported components is passed in as children/props from a Server Component parent, which is a more advanced composition pattern.

Making everything a Client Component increases your JavaScript bundle size, slows down initial page load, and prevents you from directly and safely accessing server-only resources like databases or secret API keys within those components.

Not in the same way — Client Component functions cannot be async in the way Server Components can for top-level rendering. Client Components typically fetch data using useEffect, or client-side data-fetching libraries like SWR or React Query, covered in a later lesson.

Yes, this is a valid and useful pattern. A Client Component can accept a children prop, and a parent Server Component can render a Server Component inside that children slot, letting you keep some content server-rendered even when it's visually nested inside a Client Component wrapper.

Ask whether it needs interactive state, event handlers like onClick, or browser-only APIs like localStorage or window. If yes, it needs 'use client'. If it's purely about fetching and displaying data or static structure, it can remain a Server Component.

It depends on scale — a few small, focused Client Components have minimal impact. The performance cost grows when large sections of an app, or entire pages, are unnecessarily marked as Client Components when only a small part actually needed interactivity.