[Next.js Tutorial] Server Components vs. Client Components: When to Choose Which?

If you’re working with Next.js—especially from version 13 onward with the App Router—you’ve surely heard of two game-changing concepts: Server Components and Client Components. These aren’t just new features, but a revolution in how we think about and build React applications.

Server Components and Client Components in Next.js

This article is your guide to not just "understanding" but truly mastering this powerful model. Let’s dive into every aspect!

The Core Foundation: Why This Change? 💡

Previously, in React, every component was basically a "Client Component". They were sent to the browser, hydrated, and became interactive. This model is simple but has drawbacks:

  • Large bundle size: All JavaScript code for a page must be downloaded, even for static content.
  • Data waterfalls: Nested client-side data fetching can cause delays.
  • Information leakage: API keys or tokens can accidentally be exposed in client-side code.

Next.js App Router solves these issues by introducing a hybrid model, letting you choose where each component is rendered. That’s where Server and Client Components shine.

Key point: In the App Router, all components are Server Components by default.

Server Components: The Silent "Kitchen" 🧑‍🍳

Think of a Server Component as a chef in the kitchen. They prepare everything, cook, and plate the dish perfectly before serving it to the customer. The customer (browser) only receives the final product (HTML), with no knowledge of the recipe or complex process behind the scenes.

Server Components run entirely on the server. They render before being sent to the browser, and their JavaScript code is never sent to the client.

Key Features:

  • Direct backend access: Can await data from databases, call internal APIs, read the file system (fs) safely—no need to create extra API endpoints.
  • High security: API keys, tokens, and sensitive logic are kept safe—they never leave the server.
  • Superior performance: Minimizes JavaScript sent to the client, making initial page loads (First Contentful Paint) much faster.
  • SEO friendly: Content is fully rendered on the server, making it easy for search engines to crawl and index.
  • No interactivity: Cannot use hooks like useState, useEffect, onClick, or any browser APIs. They’re for rendering UI only.

Example: A Blog Page Fetching Posts

// app/blog/[slug]/page.js
// This is a Server Component (default)

async function getPostData(slug) {
  const res = await fetch(`https://api.example.com/posts/${slug}`, {
    // Keep API key safe on the server
    headers: { Authorization: `Bearer ${process.env.API_SECRET}` },
  })
  return res.json()
}

export default async function BlogPostPage({ params }) {
  const post = await getPostData(params.slug)

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.author}</p>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  )
}

Client Components: The Interactive "Face" 🖱️

If Server Components are the kitchen, Client Components are the waiters at the table. They interact directly with users, handle requests, change state, and create a lively experience.

To make a component a Client Component, just add the directive 'use client'; at the top of the file.

Client Components are rendered on the server (SSR/SSG) and then hydrated and run in the browser, allowing them to use state, effects, and handle user events.

Key Features:

  • Full interactivity: Use useState for state, useEffect for side effects, and event handlers like onClick, onChange.
  • Access browser APIs: Use APIs like window, localStorage, geolocation.
  • Integrate client-side libraries: Easily work with libraries that only run in the browser (e.g., animation libraries).

Example: Interactive "Like" Button

// components/LikeButton.js
// This is a Client Component

'use client' // The most important directive!

import { useState } from 'react'

export default function LikeButton({ initialLikes }) {
  const [likes, setLikes] = useState(initialLikes)
  const [isLiked, setIsLiked] = useState(false)

  const handleClick = () => {
    const newLikes = isLiked ? likes - 1 : likes + 1
    setLikes(newLikes)
    setIsLiked(!isLiked)
  }

  return (
    <button onClick={handleClick}>
      {isLiked ? '❤️ Liked' : '🤍 Like'} ({likes})
    </button>
  )
}

When to Use Which? The Golden Rule 🤔

This is the most important question. Here’s a quick reference table to help you decide:

Use Server Component when you need...Use Client Component when you need...
Fetching data.Adding interactivity (onClick, onChange, ...).
Direct backend access (database, files, ...).Using state and lifecycle (useState, useEffect).
Keeping sensitive info safe.Using browser-only APIs (window, localStorage).
Reducing client-side JavaScript.Using custom hooks based on state or effects.
Rendering static, non-interactive UI.Integrating class component libraries.

Golden rule: Start with Server Components and only use Client Components ('use client';) for components that truly need interactivity at the "leaf" nodes of your component tree.

How Do Server and Client Components "Live Together"? 🤝

The real power of Next.js is in how smoothly these two types of components work together.

Using Server and Client Components Together in Next.js

1. Server Component Wrapping Client Component (Most Common)

This is the most natural model. You have a Server Component (e.g., page.js) to fetch data and render the main structure, then import and use a Client Component (e.g., LikeButton.js) inside it.

// app/blog/[slug]/page.js (Server Component)
import LikeButton from '@/components/LikeButton'

async function getPostData(slug) {
  /* ... */
}

export default async function BlogPostPage({ params }) {
  const post = await getPostData(params.slug)

  return (
    <article>
      <h1>{post.title}</h1>
      {/* ... post content ... */}

      {/* Pass data from Server to Client via props */}
      <LikeButton initialLikes={post.likes} />
    </article>
  )
}

Important note: Data passed from a Server Component to a Client Component via props must be serializable. You can’t pass functions, Dates, or other complex values that can’t be converted to a string.

2. Client Component Wrapping Server Component (Advanced Technique)

You cannot import a Server Component into a Client Component. This will cause an error!

WRONG:

// components/ClientComponent.js
'use client'
import ServerComponent from '@/components/ServerComponent' // ERROR!

export default function ClientComponent() {
  return (
    <div>
      <ServerComponent />
    </div>
  )
}

RIGHT: Use the children prop. Pass the Server Component as a children prop from a parent Server Component.

// app/layout.js (Server Component)
import ClientLayout from '@/components/ClientLayout';
import ServerSideBar from '@/components/ServerSideBar';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {/* Pass ServerSideBar as a prop to ClientLayout */}
        <ClientLayout sidebar={<ServerSideBar />}>
          {children}
        </ClientLayout>
      </body>
    </html>
  );
}

// components/ClientLayout.js (Client Component)
"use client";

export default function ClientLayout({ children, sidebar }) {
  // ... client component state and logic
  return (
    <div className="container">
      <aside>{sidebar}</aside> {/* Render Server Component here */}
      <main>{children}</main>
    </div>
  );
}

Conclusion: The Outstanding Benefits 🚀

With a solid understanding of component types in Next.js, you’ll gain many benefits:

  • Lightning speed ⚡: Less JavaScript, faster page loads, better user experience.
  • Strong security 🛡️: Business logic and sensitive data stay safe on the server.
  • Top-notch developer experience 👨‍💻: Fetch data right where you need it, making code cleaner, easier to understand, and maintain.
  • SEO optimized 📈: Static content is pre-rendered for Google bots, improving search rankings.

Server Components and Client Components aren’t two opposing worlds—they’re two perfect pieces of the modern, performant, and secure Next.js app puzzle.

Knowing when to use each and how they work together is the key to unlocking the full power of the Next.js App Router. Start thinking "server-first" and only use 'use client'; when truly needed.

Build amazing apps with Next.js!

Related Posts

[Next.js Tutorial] The Complete Guide to Styling: Which Option is Best for You?

Step-by-step guide to styling in Next.js. This article covers best practices and performance tips to help you build beautiful and fast UIs.

[Next.js Tutorial] What is Next.js? Why Should You Choose It in 2025?

Curious about Next.js? This article explains what Next.js is, why it matters for SEO and performance, and the outstanding benefits of using it.

[Next.js Tutorial] Basic Directory Structure and Routing in Your Project

How to organize your Next.js project effectively? A detailed guide on directory structure and basic routing, helping you build Next.js websites quickly and properly.

[Next.js Tutorial] Layouts: A Detailed Guide to Optimizing Your Website UI

Want to manage your Next.js website layout flexibly and efficiently? This article will guide you on using layouts to speed up development and improve user experience.