Jun 6 2025 ~ 4 min read

Next.js Architecture: Server vs Client Components


Next.js Architecture: Server vs Client Components

🧠 Understanding the Component Architecture

Next.js introduces a powerful paradigm with React Server Components that fundamentally changes how we think about React applications. Understanding when and how to use Server vs Client Components is crucial for building performant, modern web applications.

πŸ–₯️ Server Components (Default)

Server Components are the default in Next.js App Router. They run on the server and have several key characteristics:

What Server Components CAN do:

  • Fetch data directly from databases or APIs
  • Access server-side resources (file system, environment variables)
  • Render static content efficiently
  • Reduce client-side JavaScript bundle size
  • Improve initial page load performance

What Server Components CANNOT do:

  • Use browser-only APIs (localStorage, sessionStorage, etc.)
  • Handle user interactions (onClick, onChange, etc.)
  • Use React hooks like useState, useEffect, useContext
  • Access browser APIs like geolocation or camera
// This is a Server Component by default
async function ServerComponent() {
  // This runs on the server
  const data = await fetch('https://api.example.com/data');
  const posts = await data.json();

  return (
    <div>
      <h1>Posts from Server</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
        </article>
      ))}
    </div>
  );
}

🌐 Client Components

Client Components run in the browser and enable all the interactive features we’re familiar with in traditional React applications.

When to use Client Components:

  • User interactions (clicks, form inputs, hover effects)
  • Browser APIs (geolocation, camera, localStorage)
  • React hooks (useState, useEffect, custom hooks)
  • Real-time features (WebSockets, live updates)
  • Complex state management

Converting to Client Component

Add the 'use client' directive at the top of your file:

'use client'
import { useState, useEffect } from 'react'

function ClientComponent() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState(null);

  useEffect(() => {
    // This runs in the browser
    const stored = localStorage.getItem('count');
    if (stored) {
      setCount(parseInt(stored));
    }
  }, []);

  const handleClick = () => {
    const newCount = count + 1;
    setCount(newCount);
    localStorage.setItem('count', newCount.toString());
  };

  return (
    <div>
      <h1>Interactive Counter</h1>
      <p>Count: {count}</p>
      <button onClick={handleClick}>
        Increment
      </button>
    </div>
  );
}

πŸ—οΈ Best Practices & Architecture Patterns

1. Start with Server Components

Always start with Server Components and only add 'use client' when you need browser-specific functionality.

2. Minimize Client Component Boundaries

Keep your 'use client' boundaries as small as possible. Instead of making an entire page a Client Component, create smaller interactive components.

// ❌ Don't do this - entire page becomes client-side
'use client'
function Page() {
  const [likes, setLikes] = useState(0);

  return (
    <div>
      <Header />
      <StaticContent />
      <LikeButton likes={likes} onLike={() => setLikes(likes + 1)} />
      <Footer />
    </div>
  );
}

// βœ… Do this - only interactive part is client-side
function Page() {
  return (
    <div>
      <Header />
      <StaticContent />
      <LikeButton /> {/* This component has 'use client' */}
      <Footer />
    </div>
  );
}

3. Composition Pattern

You can pass Server Components as children to Client Components:

// Client Component
'use client'
function InteractiveWrapper({ children }) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>
        Toggle Content
      </button>
      {isOpen && children}
    </div>
  );
}

// Server Component
function Page() {
  return (
    <InteractiveWrapper>
      <ServerRenderedContent /> {/* Stays on server */}
    </InteractiveWrapper>
  );
}

πŸ“Š Performance Implications

Server Components Benefits:

  • Smaller bundle size - Server Component code doesn’t ship to the client
  • Faster initial load - Less JavaScript to download and parse
  • Better SEO - Content is server-rendered
  • Direct data access - No need for API routes for database queries

Client Components Benefits:

  • Immediate interactivity - No round-trip to server for state changes
  • Rich user experience - Full access to browser APIs
  • Offline capabilities - Can work without server connection

πŸ”— Additional Resources

More info: Next.js App Router - Server vs Client Components

Headshot of Alvaro Uribe

Hi, I'm Alvaro. I'm a software engineer from Chile πŸ‡¨πŸ‡± based in New Zealand πŸ‡³πŸ‡Ώ.