Hydration Mismatch – When SSR and CSR Start Beef


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
Mistake | What It Did |
<p>{new Date()}</p> | Random value on each render = mismatch |
Accessing localStorage directly | Crashed 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 💧
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.