July 7, 2026·7 min read
React vs Next.js: The Full Picture
Everything you need to understand about React and Next.js - how rendering works under the hood, what SSR/CSR/SSG actually mean, and when to pick one over the other.
React and Next.js are not competitors. React is a UI library - it gives you components, state, and hooks. Next.js is a framework built on top of React that handles everything else: routing, rendering, bundling, and deployment.
Think of it this way: React is the engine. Next.js is the full car.
When you build a React app with Vite or Create React App, everything runs in the browser. This is called Client-Side Rendering (CSR).
Here's the complete flow of what happens when someone visits your site:
USER types your URL and hits Enter
│
▼
BROWSER sends a request to your server
│
▼
SERVER responds with a tiny HTML file:
│
│ <!DOCTYPE html>
│ <html>
│ <body>
│ <div id="root"></div> ← completely empty
│ <script src="/bundle.js" /> ← all your code lives here
│ </body>
│ </html>
│
▼
BROWSER receives the HTML
│
├── Renders the empty <div> → user sees a BLANK WHITE SCREEN
│
▼
BROWSER starts downloading bundle.js
│
│ This file contains:
│ • React library code (~140KB)
│ • Your components (App, Header, Footer, etc.)
│ • Your router (react-router-dom)
│ • Your state management (Redux, Zustand, etc.)
│ • All third-party libraries
│ • Total: often 300KB–2MB
│
▼
BROWSER parses and executes the JavaScript
│
▼
REACT takes over:
│
├── Creates a Virtual DOM (a JS object representing your UI)
├── Diffs it against the real DOM (empty <div>)
├── Injects all your HTML into the page
│
▼
COMPONENTS mount and start calling useEffect()
│
├── Component A: fetch("/api/user") → waits 200ms
├── Component B: fetch("/api/posts") → waits 300ms
├── Component C: fetch("/api/comments") → waits 150ms
│
│ ⚠️ These often happen SEQUENTIALLY (waterfall)
│ because Component B might depend on Component A's data
│
▼
DATA arrives → Components re-render with actual content
│
▼
✅ USER finally sees the complete page
Total time: 2-5 seconds on average
The key problem: The user stares at nothing until JavaScript downloads, parses, runs, fetches data, and renders. On slow connections or old phones, this takes forever.
Here's what a typical CSR component looks like:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// This runs AFTER the component mounts in the browser
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, [userId]);
if (loading) return <Spinner />;
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}The useState → useEffect → fetch → re-render cycle is the signature pattern of CSR. Every data-dependent component follows this dance.
Next.js flips the model. Instead of sending empty HTML and making the browser do everything, the server builds the complete page first.
USER types your URL and hits Enter
│
▼
BROWSER sends a request to your server
│
▼
SERVER receives the request and does ALL the work:
│
├── 1. Looks at the URL → finds the matching page component
│
├── 2. Runs your React components ON THE SERVER
│ (not in the browser - on the server machine)
│
├── 3. Fetches all data DIRECTLY:
│ • Queries the database
│ • Calls APIs
│ • Reads files
│ • All in parallel - no waterfall!
│
├── 4. Renders components to HTML strings
│ React's renderToString() converts your JSX
│ into actual HTML tags
│
├── 5. Sends complete HTML to the browser:
│
│ <!DOCTYPE html>
│ <html>
│ <body>
│ <h1>Shivam Gaur</h1>
│ <p>Full-stack developer...</p>
│ <div class="projects">
│ <div class="card">Project 1</div>
│ <div class="card">Project 2</div>
│ </div>
│ </body>
│ </html>
│
│ (fully populated - every element is already there)
│
▼
BROWSER receives the complete HTML
│
├── Renders it IMMEDIATELY → user sees the full page
│
▼
BROWSER downloads a small JS bundle in the background
│
│ This bundle is MUCH smaller because:
│ • Server Components send ZERO JavaScript
│ • Only interactive parts (buttons, forms) need JS
│ • Typically 50KB–200KB instead of 500KB–2MB
│
▼
HYDRATION happens:
│
│ React "attaches" event listeners to the existing HTML
│ • onClick handlers activate
│ • Forms become submittable
│ • Animations start working
│ • The page was already VISIBLE - now it's INTERACTIVE
│
▼
✅ USER saw the page in ~200ms, interactive in ~500ms
The key advantage: The user sees content almost instantly. No blank screen. No loading spinners. The server did the heavy lifting.
The same component in Next.js:
// This function runs on the SERVER - never in the browser
async function UserProfile({ params }) {
const { userId } = await params;
// Direct database query - no API call needed
const user = await db.query(
'SELECT * FROM users WHERE id = ?',
[userId]
);
// Returns pure HTML - no useState, no useEffect
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}No loading states. No useEffect. The server fetches data and sends finished HTML.
SSG is even faster than SSR. Instead of rendering on every request, Next.js builds the HTML once during deployment:
BUILD TIME (when you run "npm run build")
│
▼
Next.js finds all your pages
│
├── / → runs Home component → generates index.html
├── /about → runs About component → generates about.html
├── /blog/react-vs-nextjs → generates this exact page you're reading
│
▼
Output: static HTML files sitting on a CDN
│
▼
RUNTIME (when a user visits)
│
├── CDN serves the pre-built HTML file
├── No server computation at all
├── Response time: ~20ms (just file delivery)
│
▼
✅ Fastest possible rendering strategy
This blog post uses SSG. The HTML was generated at build time and is served as a static file.
This is the biggest real-world performance difference.
CSR - Sequential waterfall:
<App> mounts
└── fetches /api/user ─────────── 200ms wait
│
▼
<Dashboard> renders with user data
└── fetches /api/orders ──── 300ms wait
│
▼
<OrderList> renders
└── fetches /api/items ── 150ms wait
Total: 200 + 300 + 150 = 650ms of sequential waiting
Each component waits for its parent to finish before it even starts fetching.
Next.js - Parallel on the server:
Server receives request
│
├── fetches user data ──── 200ms ─┐
├── fetches orders ─────── 300ms ─┤ all at once!
└── fetches items ──────── 150ms ─┘
│
▼
Total: 300ms (the slowest one wins)
│
▼
Complete HTML sent to browser
React - manual config with react-router-dom:
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/blog/:slug" element={<BlogPost />} />
</Routes>
</BrowserRouter>Next.js - your folder structure IS the router:
app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
├── blog/
│ ├── page.tsx → /blog
│ └── [slug]/
│ └── page.tsx → /blog/react-vs-nextjs
└── layout.tsx → wraps every page
No config. No imports. Create a folder, add page.tsx, done.
| React (CSR) | Next.js (SSR/SSG) | |
|---|---|---|
| HTML sent to browser | Empty <div> | Complete page |
| Where JS runs | 100% in browser | Mostly on server |
| Bundle size | Large (everything) | Small (interactive parts only) |
| First paint | Slow (2-5s) | Fast (~200ms) |
| SEO | Poor | Excellent |
| Data fetching | useEffect + fetch | async/await in components |
| Routing | react-router-dom | File-based (automatic) |
| Loading states | You build them | Mostly unnecessary |
Plain React → Internal dashboards, admin panels, highly interactive apps (Figma, Notion-type). Anything behind a login where SEO doesn't matter.
Next.js → Public websites, blogs, portfolios, e-commerce, SaaS landing pages. Anything where Google needs to find you and users expect instant loads.
React gives you components. Next.js gives you a production system around those components - handling the rendering, routing, bundling, and optimization so you don't have to wire it all up yourself.
You're writing React either way. The difference is where and when that React code runs.