π§ The Toddler Explanation
Imagine you're drawing a picture of a house. There are two ways to show your friend this picture:
Way 1 (SSR - Server-Side Rendering): You draw the entire house with all the colors, windows, and doors BEFORE showing it to your friend. When they see it - BAM! - the whole house is already there, complete and ready to look at.
Way 2 (Client-Side Rendering): You give your friend a blank paper and crayons, and they have to draw the house themselves. It takes them time to draw it, so they see a blank page first, then slowly the house appears.
SSR is like Way 1 - the website is already drawn and ready when you open it. It's faster to see and makes everyone happy! π¨β¨
π Why Developers Use SSR
Speed & Performance
- Faster First Paint: Users see content immediately instead of staring at a blank screen or loading spinner
- Better SEO: Search engines like Google can read your fully-rendered HTML right away
- Improved Performance on Slow Devices: The server does the heavy lifting, not the user's phone
Real-World Benefits
Traditional CSR: Blank β Spinner β Content (3-5 seconds)
Next.js SSR: Instant Content β Interactive (1-2 seconds)
π§βπ» The Deep Dive: How SSR Actually Works
The SSR Lifecycle
-
User Requests Page β Browser asks server for
/products - Server Renders React β Server executes your React components and generates HTML
- HTML Sent to Browser β Fully rendered HTML travels to the client
- Browser Displays HTML β User sees content immediately (non-interactive)
- JavaScript Downloads β React bundle loads in the background
- Hydration β React "attaches" to the existing HTML, making it interactive
Code Example
// pages/products.js
export async function getServerSideProps() {
// This runs on the SERVER for every request
const res = await fetch('https://api.example.com/products');
const products = await res.json();
return {
props: { products }, // Passed to component as props
};
}
export default function Products({ products }) {
// This runs TWICE:
// 1. On server (generates HTML)
// 2. On client (hydrates the HTML)
return (
<div>
<h1>Our Products</h1>
{products.map(product => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}
β οΈ The Hydration Mismatch Problem
What Is Hydration?
Hydration is when React takes the server-rendered HTML and "wakes it up" by attaching event listeners and state. React expects the HTML it generates on the client to match EXACTLY what the server sent.
The Mismatch Horror Story
// β This WILL cause hydration errors
export default function BadComponent() {
return (
<div>
<p>Current time: {new Date().toLocaleTimeString()}</p>
</div>
);
}
Why it breaks:
- Server renders at 10:30:15 β HTML shows "10:30:15"
- Client hydrates at 10:30:17 β React expects "10:30:17"
- MISMATCH! β Console error and potential UI bugs
Common Causes of Hydration Mismatches
- Random/Time-Based Values
{Math.random()} // Different every render
{Date.now()} // Different server vs client
- Browser-Only APIs
{window.innerWidth} // window doesn't exist on server
{localStorage.getItem('theme')} // localStorage is undefined on server
- Conditional Rendering Based on Client State
{isClient && <Component />} // Server sees nothing, client sees something
- Third-Party Libraries
<SomeLibrary /> // Library might access window or document
β Solutions to Hydration Mismatches
Solution 1: useEffect for Client-Only Code
'use client';
import { useState, useEffect } from 'react';
export default function SafeComponent() {
const [time, setTime] = useState(null);
useEffect(() => {
// Runs ONLY on client after hydration
setTime(new Date().toLocaleTimeString());
}, []);
return (
<div>
<p>Current time: {time || 'Loading...'}</p>
</div>
);
}
Solution 2: Suppress Hydration Warning (Use Sparingly)
<div suppressHydrationWarning>
{new Date().toLocaleTimeString()}
</div>
Solution 3: Dynamic Imports with No SSR
import dynamic from 'next/dynamic';
const DynamicComponent = dynamic(
() => import('../components/ClientOnly'),
{ ssr: false } // Skips SSR for this component
);
export default function Page() {
return <DynamicComponent />;
}
Solution 4: Check Environment
const isBrowser = typeof window !== 'undefined';
export default function Component() {
const width = isBrowser ? window.innerWidth : 0;
return <div>Width: {width}</div>;
}
π― Best Practices
DO β
- Fetch data in
getServerSidePropsorgetStaticProps - Use
useEffectfor browser-only code - Keep server and client renders identical initially
- Use dynamic imports for third-party libraries that need
window
DON'T β
- Access
window,document, orlocalStorageduring render - Generate random values or timestamps during render
- Use different logic for server vs client renders
- Forget that your component runs twice (server + client)
π Debugging Hydration Issues
When you see: Warning: Text content did not match...
- Check the DOM: Use React DevTools to compare server HTML vs client render
-
Console Log: Add
console.log('SERVER')vsconsole.log('CLIENT')to track execution - Isolate: Comment out components until the error disappears
- Search for: Time, random numbers, browser APIs, or conditional rendering
π¬ Conclusion
SSR with Next.js gives you the best of both worlds: fast initial page loads with the interactivity of React. The hydration process is mostly magical, but understanding when server and client diverge will save you hours of debugging.
Remember: Server and client must start identical. Diverge only after hydration with useEffect.
Happy coding! π
Found this helpful? Follow me for more Next.js tips and tricks!
Top comments (0)