The React Paradigm Shift
With the release of the App Router in Next.js and the widespread adoption of React Server Components (RSC), the entire mental model of how we build React applications has been flipped.
For the past seven years, developers have been intimately familiar with Single Page Applications (SPAs) where highly dynamic, stateful JavaScript payloads are sent to the browser, which then hydrates and renders the layout. This created an amazing developer experience but often led to massive bundle sizes and abysmal loading speeds for users on poor connections.
What are Server Components?
By default, all components in the Next.js App Router are Server Components. They never hydrate on the client. They execute entirely on the server and stream serialized HTML/UI down to the browser.
The Benefits
- Zero Client JavaScript: The code for the component itself isn't shipped to the browser, massively reducing bundle sizes.
- Direct Database Access: You can query databases directly inside your component without needing an intermediary API route.
- Secure by Default: Secrets (like API keys) stay on the server and are never exposed to the client.
How Data Fetching Changes
In the old world, you would use useEffect and fetch data on mount, leading to annoying loading spinners. In the RSC world, you simply make your component an async function.
// This runs on the server. No hooks needed.
export default async function Dashboard() {
const user = await db.query('SELECT * FROM users WHERE id = 1');
return (
<div>
<h1>Welcome {user.name}</h1>
</div>
)
}
When to use Client Components
You must explicitly add the "use client" directive to opt-in to Client Components. Use them precisely when:
- You need access to browser APIs (like
windoworlocalStorage). - You need interactivity (like
onClick,onChange). - You need React state or lifecycle hooks (
useState,useEffect).
The Interleaved Architecture
The true power of this architecture lies in combining them. It is not an "either/or" situation. The ideal Next.js application is a massive tree of Server Components with tiny "leaves" of Client Components sprinkled in exactly where interactivity is required.
Pass Server Components as
childrenprops to Client Components to maintain server-side rendering benefits within interactive shells.
Summary
Stop thinking in terms of pages. Start thinking in terms of boundaries. Push interactivity as far down the component tree as possible (leaving it at the "leaves") to maximize the performance surface area of your application.