Hydration Mismatch – When SSR and CSR Start Beef

Faiaz KhanFaiaz Khan
4 min read

So you built a handsome good lookin SSR-powered app with Next.js, shipped it with excitement, and then…

⚠️ "Warning: Text content did not match. Server: 'Hello' Client: 'Hi'"

Your hydration mismatch has entered the chat.

This isn’t just a warning — it’s your app screaming for help because the server rendered one thing and the client said “nah fam, I got different data.”

Let’s break down what hydration is, why it breaks, and how to fix this beef between SSR and CSR.


First of All… What is Hydration?

In SSR (Server-Side Rendering), your server renders the HTML and sends it to the browser.

Once the browser loads that static HTML, React "hydrates" it — meaning it attaches interactivity and event handlers to that HTML.

Hydration is React’s way of saying: “Cool, I see the structure. Now let me make it do things.”

But if your server-rendered HTML doesn't match what React expects on the client, it throws a hydration warning/error.


What Causes This Beef?

Here’s the real drama: the server renders one version of your app. Then the client JavaScript kicks in, re-renders the app, and they don’t match.

Why?

🔁 1. Non-deterministic Rendering (Time, Random, Math)

<p>{new Date().toLocaleTimeString()}</p>
  • Server: 11:00:01

  • Client (a few ms later): 11:00:02

BOOM: Hydration mismatch.


📦 2. Local Storage or Window Usage in Initial Render

<p>{window.innerWidth}</p> // 🚨 breaks SSR

window is not defined on the server, so React either crashes or renders nothing → mismatch.


3. Async Data or SWR Differences

const { data } = useSWR("/api/user");
  • Server renders before data is fetched

  • Client fetches immediately after hydration → content changes

Hydration panic ensues.


4. useEffect() Side Effects That Alter DOM

useEffect(() => {
  setState("client-only text");
}, []);
  • Server never sees this

  • Client renders different thing after hydration

Mismatch strikes again.


5. Font or Styling Shifts (CLS)

Fonts that load late or style shifts can also cause visual hydration mismatches, even if the HTML technically matches.


🛠️ How to Fix It Like a Boss

Let’s make peace between your client and server like a React diplomat 🕊️


✅ 1. Gate Client-Only Code with typeof window or useEffect

const [width, setWidth] = useState(null);

useEffect(() => {
  setWidth(window.innerWidth);
}, []);

This ensures window is never accessed on the server.

Or safely guard it:

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

✅ 2. Use next/dynamic for Lazy Client-Only Components

const NoSSRComponent = dynamic(() => import("../components/ClientOnly"), {
  ssr: false,
});

This says: “Don’t render this on the server at all. Let client handle it.”

Perfect for things like modals, charts, and local storage-based components.


✅ 3. Use suppressHydrationWarning (if you must)

<span suppressHydrationWarning>{value}</span>

Use this only when:

  • You know mismatch will happen

  • It’s harmless

  • You’ve warned your future self

This doesn’t fix it — it just hides the warning. Be careful with this power.


✅ 4. Hydration-Safe Data Fetching

Use getServerSideProps / getStaticProps properly to fetch data on the server.

For SWR/React Query:

  • Hydrate data via props

  • Use fallbackData to keep consistency between server and client


✅ 5. Test in Production Mode

Dev mode is forgiving.
Prod mode shows you what users see.

npm run build && npm start

Hydration issues often only appear in prod. So always test builds.


🧪 Debug Like a Pro

Check the hydration mismatch warning — React tells you what it expected vs what it got.

📦 In Next.js:

  • Use DevTools → Network tab to compare SSR HTML vs hydrated DOM

  • Use console.log("SSR vs CSR", isServer) to debug side effects


Real-Life “Oops” Moments

MistakeWhat It Did
<p>{new Date()}</p>Random value on each render = mismatch
Accessing localStorage directlyCrashed on server
<Chart data={localData} />SSR didn’t know data yet
Using Math.random() in JSX🤡 Hydration mismatch every time

Final Thoughts

Hydration mismatch isn't just a dev console scare — it means your app is out of sync with itself.

To keep SSR and CSR in harmony:

  • Keep initial renders deterministic

  • Delay browser-only behavior

  • Use SSR-friendly data handling

  • And never trust new Date() inside JSX 😤


📚 This was part of It Worked in Dev — the blog where frontend dreams meet deployment reality (and hopefully survive it).

Stay tuned for the next post on asset path failures and source map salvation.

🚢 Ship safe. Hydrate responsibly 💧

0
Subscribe to my newsletter

Read articles from Faiaz Khan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Faiaz Khan
Faiaz Khan

Hey! I'm Faiaz — a frontend developer who loves writing clean, efficient, and readable code (and sometimes slightly chaotic code, but only when debugging). This blog is my little corner of the internet where I share what I learn about React, JavaScript, modern web tools, and building better user experiences. When I'm not coding, I'm probably refactoring my to-do list or explaining closures to my cat. Thanks for stopping by — hope you find something useful or mildly entertaining here.