#nextjs #rsc #client-components #rendering

Day 8 — Server vs. Client Components: The Paradigm Shift

🧭 Day 8 — Server vs. Client Components: The Paradigm Shift

Zero to Hero — Hands-on Next.js Tutorial

Welcome to Week 2! This week is all about Data and Rendering. We start with the most critical concept in the App Router: React Server Components (RSC).


🟦 1. The Default: Server Components

In the app directory, every component is a Server Component by default.

What does this mean?

  • Zero Bundle Size: The code for this component is NOT sent to the browser.
  • Direct Database Access: You can write db.query() directly inside your component.
  • Async/Await: You can make your component async and await data.
  • No Interactivity: You CANNOT usage useState, useEffect, or onClick.
// This is a Server Component. It runs ONLY on the server.
export default async function Page() {
  const data = await db.posts.findMany(); // secure!
  console.log("This prints in the server terminal, not the browser console");
  
  return (
    <div>
      {data.map(post => <h2 key={post.id}>{post.title}</h2>)}
    </div>
  );
}

🟩 2. The Opt-In: Client Components

If you need interactivity (clicking, state, hooks), you must “opt-in” to the client. Add the 'use client' directive at the top of your file.

When to use Client Components:

  • Event listeners (onClick, onChange).
  • State and Hooks (useState, useEffect).
  • Browser-only APIs (localStorage, window).
  • Class components (rare nowadays).
'use client'; // This marks the "Client Boundary"

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0); // Hooks work here!

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

🟧 3. The “Network Boundary”

Think of your app as a tree. The root is on the Server. You render Server Components down the tree. Eventually, you hit a leaf that needs interactivity (like a Button or Search Bar). You mark that file 'use client'.

Crucial Rule: A Client Component CANNOT import a Server Component.

  • ❌ You cannot import ServerComp into ClientComp.
  • ✅ You CAN pass a ServerComp AS A CHILD (prop) to a ClientComp.

The “Hole in the Donut” Pattern:

// ClientWrapper.tsx ('use client')
export default function ClientWrapper({ children }) {
  const [isOpen, setIsOpen] = useState(false);
  return <div>{isOpen && children}</div>;
}

// Page.tsx (Server)
import ClientWrapper from './ClientWrapper';
import ServerContent from './ServerContent';

export default function Page() {
  return (
    <ClientWrapper>
      <ServerContent /> {/* This works! */}
    </ClientWrapper>
  );
}

🧪 Challenge: Day 8

  1. Create a ServerComponent.tsx that logs “Hello from Server”.
  2. Create a ClientComponent.tsx with a button that alerts “Hello from Client”.
  3. Try to import the Server Component inside the Client Component. Watch it fail (or degrade to client).
  4. Fix it by passing the Server Component as children to the Client Component in your page.tsx.

See you tomorrow for Data Fetching! 📡