Modern React + Next.js #1: Why Next.js and Server Components
If you’ve finished the React Basics course (#1–#15) and the Todo App build series, you have nearly all the fundamentals of client-side React under your belt. In this series we step up a level and cover Modern React’s new paradigm — Server Components and the framework that handles it best, Next.js.
The series is six parts.
- #1 Why Next.js and Server Components ← this post
- #2 Getting started with Next.js and the App Router
- #3 Server Components vs Client Components
- #4 Data fetching and caching
- #5 Handling loading with Suspense and
use() - #6 Server Actions and forms
This post has almost no code. Making it clear why the new model is needed and what it solves is critical to understanding the rest.
CSR — what we’ve been doing #
Every React app we’ve built so far has used CSR (Client-Side Rendering).
1. Browser receives empty HTML (just a shell with <div id="root"></div>)
2. Downloads JavaScript bundle
3. Executes JavaScript → React renders the screen
4. If needed, fetches data → re-rendersThe advantages are clear.
- Page transitions are fast once the app is loaded (the SPA benefit covered in #15)
- The server only needs to host static files (simple deployment)
- Strong for highly interactive UIs
But the downsides are clear too.
Drawback 1. The first screen is slow to appear #
The browser receives an empty page, downloads JavaScript, executes it, and only then does the screen render. On a slow connection or weak device, you stare at a blank page for a long time.
Drawback 2. SEO is hard #
Search engine crawlers don’t run JavaScript, or run it late. If they only see empty HTML, they can’t index your content, hurting your search visibility.
Drawback 3. The JavaScript bundle grows #
All the code needed to render the screen (components, libraries, data-handling logic) has to be downloaded to the client. Every library you add increases the size users have to download.
Drawback 4. Data fetching becomes a waterfall #
Server response → JS download → JS execution → fetch → another fetch → … waiting accumulates at every step.
SSR — building HTML on the server first #
The classic solution is SSR (Server-Side Rendering), returning to the traditional web server model used by PHP or Ruby on Rails.
1. Browser requests the page
2. Server runs React and produces HTML
3. Sends the finished HTML (with content already in it)
4. Browser displays that HTML
5. Meanwhile the JS bundle is downloaded and interactivity is enabled (hydration)The word hydration appears here. It means attaching event handlers via JavaScript to already-rendered static HTML to make it a live, interactive component. The metaphor is pouring water (JS) onto dry HTML to bring it to life.
SSR solves the first-screen-speed and SEO problems. But all of the component code still has to be sent to the client and hydrated, so the bundle-size problem remains.
RSC — React Server Components #
Here the React team takes another step. “Aren’t there components that don’t need to go to the client at all?”
Think about the body of a blog post. After it’s rendered, there isn’t much for the user to click or type into. It just fetches data and displays it on screen. For a component like that, you can run it only on the server and send just the HTML (or something close to it) to the client. There’s no reason to ship the component’s JavaScript code to the client.
That’s the core idea behind React Server Components (RSC).
1. Browser requests the page
2. Server executes Server Components
3. Server Components can call the database / APIs directly
4. Result is sent in serialized form (HTML + placeholders for client components)
5. Client displays the received HTML + hydrates only the Client ComponentsWhat RSC solves:
- Smaller bundles — the code for static components doesn’t go to the client at all
- Simpler data fetching — just
await db.query(...)in a Server Component (no network round-trip) - Sensitive data is protected — API keys and DB credentials are never exposed to the client
- SEO and first-screen speed — keeps SSR’s benefits
So what runs where #
This is the core concept that will keep coming up throughout the series. Get it firmly in mind.
| Component type | Where it runs | Available | Not available |
|---|---|---|---|
| Server Components | Server only (once) | Direct DB access, environment variables, the fs module, async/await | useState, useEffect, event handlers, browser APIs |
| Client Components | Server (SSR) + client (hydration) | useState, useEffect, events, browser APIs | Direct DB access (security), fs |
The two coexist within the same page. Static content of the page is a Server Component, and parts that need interaction (forms, toggles, dropdowns) are Client Components. Drawing that boundary well is the heart of designing a modern React app.
Relationship to Next.js #
React Server Components are a feature of React itself, but using them in practice requires build tooling and server infrastructure. Someone has to serialize RSC, handle routing, manage caching, and run the server.
Next.js is the meta-framework that collaborates most closely with the React team and is the most mature option that officially supports RSC (other options like Remix and Waku exist too). We’ll use Next.js in this series.
Two key mindset shifts #
Before starting the series, two mindsets matter most:
1. Always be aware of “where does this code run?” #
Until now we lived in a single environment where “all code runs in the browser.” In modern React, code that runs on the server and code that runs on the client can mix in the same file. Without being aware of this boundary you’ll get confused fast.
Always ask yourself: “Does this function run on the server or on the client?” Then it becomes clear how data fetching should work, whether you can access environment variables, and whether you can attach event handlers.
2. Default to Server Component, opt into Client Component #
When creating a new component, the default is a Server Component (in Next.js’s App Router). You only convert it by adding the 'use client' directive when there’s a clear reason (state, events, browser APIs). This default is the key to keeping bundles small.
What you’ll learn in this series #
What we’ll cover step by step from the next post:
- #2 Starting a Next.js project, App Router file structure, layout system
- #3 The
'use client'directive and the boundary between server and client components - #4 The
await fetch(...)pattern in Server Components, caching - #5 Handling loading with Suspense,
loading.tsx, and theuse()hook - #6 Form submission and mutations with Server Actions
At the end we’ll combine everything in a small mini project (a simple message-style app).
Wrap-up #
In this post, we covered why a new model is needed.
- CSR — fast interactions ✅, slow first screen / weak SEO / large bundle ❌
- SSR — fast first screen / good SEO ✅, all code still sent to the client ❌
- RSC — static content runs only on the server and the code itself never goes to the client
Two mindsets to lock in:
- Always be aware of “where does this code run?”
- Default to Server Component, opt into Client Component
In the next post, “Modern React + Next.js #2: Getting started with Next.js and the App Router,” we’ll actually create a Next.js project and get hands-on with the App Router’s file-based routing and layout system.