DEV Community

Prashant Sharma
Prashant Sharma

Posted on

What's Server-Side Rendering (SSR) with Next.js, and Why Use It?

πŸ§’ 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)
Enter fullscreen mode Exit fullscreen mode

πŸ§‘β€πŸ’» The Deep Dive: How SSR Actually Works

The SSR Lifecycle

  1. User Requests Page β†’ Browser asks server for /products
  2. Server Renders React β†’ Server executes your React components and generates HTML
  3. HTML Sent to Browser β†’ Fully rendered HTML travels to the client
  4. Browser Displays HTML β†’ User sees content immediately (non-interactive)
  5. JavaScript Downloads β†’ React bundle loads in the background
  6. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

⚠️ 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

  1. Random/Time-Based Values
   {Math.random()} // Different every render
   {Date.now()} // Different server vs client
Enter fullscreen mode Exit fullscreen mode
  1. Browser-Only APIs
   {window.innerWidth} // window doesn't exist on server
   {localStorage.getItem('theme')} // localStorage is undefined on server
Enter fullscreen mode Exit fullscreen mode
  1. Conditional Rendering Based on Client State
   {isClient && <Component />} // Server sees nothing, client sees something
Enter fullscreen mode Exit fullscreen mode
  1. Third-Party Libraries
   <SomeLibrary /> // Library might access window or document
Enter fullscreen mode Exit fullscreen mode

βœ… 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Solution 2: Suppress Hydration Warning (Use Sparingly)

<div suppressHydrationWarning>
  {new Date().toLocaleTimeString()}
</div>
Enter fullscreen mode Exit fullscreen mode

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 />;
}
Enter fullscreen mode Exit fullscreen mode

Solution 4: Check Environment

const isBrowser = typeof window !== 'undefined';

export default function Component() {
  const width = isBrowser ? window.innerWidth : 0;

  return <div>Width: {width}</div>;
}
Enter fullscreen mode Exit fullscreen mode

🎯 Best Practices

DO βœ…

  • Fetch data in getServerSideProps or getStaticProps
  • Use useEffect for 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, or localStorage during 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...

  1. Check the DOM: Use React DevTools to compare server HTML vs client render
  2. Console Log: Add console.log('SERVER') vs console.log('CLIENT') to track execution
  3. Isolate: Comment out components until the error disappears
  4. 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)