In traditional React architectures, a strict binary existed: either your entire application ran on the client (SPA), or the server generated initial HTML (SSR) but still shipped 100% of the component JavaScript to the browser for full hydration.
Next.js 16 and React 19 introduce a foundational dual component paradigm:
DATABASE_URL, API secrets). RSC code and dependencies never ship to the client browser bundle (Zero Bundle Size).'use client' directive. They render on the server to output initial HTML and then hydrate in the browser, unlocking hooks (useState, useEffect), browser APIs (localStorage, geolocation), and interactive event listeners (onClick, onChange).The 'use client' directive does not mean "execute only in the browser." Instead, it declares a Network Boundary: everything imported within or below this file is packaged by the bundler into the client JavaScript bundle.
Visualizing the component tree and the hydration boundary:
+-------------------------------------------------------------------------+
| MENTAL MODEL: THE 'use client' BOUNDARY |
+-------------------------------------------------------------------------+
| SERVER REALM (Node.js runtime, direct DB access, zero JS in bundle): |
| [Page (RSC)] ---> queries database directly: SELECT * FROM products |
| | |
| +--- [ProductHeader (RSC)] ---> static server-rendered presentation |
| | |
| ===== 'use client' NETWORK BOUNDARY =================================== |
| | |
| CLIENT REALM (Downloaded by browser, hydrated, interactive): |
| +--- [AddToCartButton ('use client')] |
| - uses useState, onClick handlers |
| - executes client transitions and preserves local state |
+-------------------------------------------------------------------------+All Server Components run in Node.js, querying databases and reading backend secrets without network hops.
Next.js serializes server output into the React Flight stream and renders initial HTML for fast First Contentful Paint.
Client Components execute on the server to contribute visual HTML structure (SSR) before browser hydration.
The browser downloads only client-boundary JavaScript chunks and attaches event handlers to interactive DOM islands.
What is the exact technical function of placing `'use client'` at the very top of a component file?
process.env.STRIPE_SECRET_KEY inside Server Components with zero risk of browser exposure.'use client' strictly to interactive leaf buttons or search inputs, keeping more than 90% of the route code purely on the server.children Prop: Passing heavy Server Component subtrees through interactive Client modal dialogs without forcing the heavy content into the client bundle.Server Components can be native async/await functions without needing useEffect or client state:
// Default Server Component — no 'use client' directive!
interface Product {
id: string;
name: string;
price: number;
}
// Direct async query to server data sources
async function getProducts(): Promise<Product[]> {
// Direct database query or internal fetch
const res = await fetch('https://api.wpskills.dev/v1/products', {
next: { revalidate: 60 },
});
return res.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
<div className="max-w-5xl mx-auto py-8">
<h1 className="text-2xl font-bold mb-6">Hardware Catalog</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{products.map(product => (
<div key={product.id} className="p-4 rounded-xl border border-border bg-bg-surface">
<h3 className="font-semibold text-text-primary">{product.name}</h3>
<p className="text-accent-cyan font-bold mt-2">${product.price}</p>
{/* Interactive client-side button island */}
<AddToCartButton productId={product.id} />
</div>
))}
</div>
</div>
);
}Apply 'use client' strictly to the interactive leaf component:
'use client'; // REQUIRED: Establishes the client boundary!
import React, { useState } from 'react';
export function AddToCartButton({ productId }: { productId: string }) {
const [isAdded, setIsAdded] = useState(false);
const handleAdd = () => {
setIsAdded(true);
// Add to cart logic (localStorage or Zustand store)
console.log(`Product ${productId} added to cart`);
};
return (
<button
onClick={handleAdd}
className={`mt-4 w-full py-2 px-4 rounded-lg text-sm font-semibold transition-colors ${
isAdded
? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
: 'bg-accent-cyan text-bg-base hover:opacity-90'
}`}
>
{isAdded ? 'Added to Cart ✓' : 'Add to Cart'}
</button>
);
}A Client Component cannot directly import a Server Component (import ServerComp from './ServerComp' converts it into a Client Component).
Solution: Pass the Server Component via the children prop!
// 1. Client container (ModalWrapper.tsx):
'use client';
import { useState } from 'react';
export function ModalWrapper({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
{isOpen && <div className="modal-backdrop">{children}</div>}
</div>
);
}
// 2. Server page (page.tsx):
// ServerComponent renders on the server and is passed as pre-rendered children!
export default function Page() {
return (
<ModalWrapper>
{/* HeavyServerTable executes ON THE SERVER and adds zero bytes to the client bundle! */}
<HeavyServerTable />
</ModalWrapper>
);
}Why does passing a Server Component as a `children` prop into a Client Component (`<ClientModal><ServerList /></ClientModal>`) preserve its execution on the server?
Adding 'use client' at the top of page.tsx transforms the entire page and every module it imports into client-side code, forfeiting all performance benefits of Server Components.
Solution: Keep route pages and layouts as Server Components, and push 'use client' down the component tree to specific interactive buttons, inputs, and tabs (Move Client Boundary Down).
Server Components transmit props to Client Components across the network via the Flight serialization protocol. You cannot pass functions, custom class instances, or circular references:
// ERROR: Functions cannot be serialized across the Server -> Client boundary!
<ClientComponent onClick={() => console.log('server')} />Event handlers must be defined inside Client Components or passed via Server Actions ('use server').