DEV Community

Cover image for Hydration Error in Next.js: Why It Happens and How to Fix It
Lavkush kumar
Lavkush kumar

Posted on Originally published at codingcush.com

Hydration Error in Next.js: Why It Happens and How to Fix It

Originally published on CodingCush.

A hydration error in Next.js happens when the HTML your server sends does not match what React expects to render in the browser on the first load. React compares the two, finds different text, different tags, or extra elements, and complains. The good news is simple: your app is usually not broken in a deep way. One small piece of code is rendering something different on the server and on the client. Find that piece, make both sides agree, and the error goes away.

Think of two cooks making the same dish: the server cooks first and sends a finished page, then React in the browser cooks the same page again to make it interactive. If the plates do not match, React gets confused. Let us find the real culprit quickly and fix each common cause.

What Does Hydration Actually Mean?

Hydration is the step where a static-looking HTML page becomes alive. The server sends HTML so the page appears fast and search engines can read it. Then React loads in the browser, attaches click handlers, state and effects to that existing HTML, and takes control. That hand-over only works smoothly if React finds the structure it expected.

If the server-versus-client split is still fuzzy, our guide on server components vs client components in Next.js explains who renders what. Once you see that split, hydration errors stop feeling random — they live on the border between the two sides.

Next.js has an official page for this exact message: the Next.js hydration error documentation lists the common causes, from invalid nesting to browser-only code in the first render. Keep it open while you debug.

The 5 Most Common Causes

In real projects, the same five culprits appear again and again. Check these in order — most developers find their bug in the first two.

1. Invalid HTML nesting. A paragraph inside a paragraph, a div inside a paragraph, or a button inside a button. Browsers quietly fix invalid nesting by moving tags around, so the HTML React receives is no longer the HTML the server sent. This is the number one hidden cause, and it rarely looks like a JavaScript bug at all.

2. Browser-only values in the first render. Code that uses window, localStorage, or document during rendering gives one answer on the server — where those things do not exist — and a different answer in the browser.

3. Dates, times and random numbers. A line like new Date().toLocaleString() or Math.random() will almost never produce the same value twice. The server says 10:01, the browser says 10:02, and React quite correctly reports a mismatch.

4. Data that differs between server and client. Fetching different data on each side, or reading a value that changes between the two renders, produces different text in the same place.

5. Browser extensions and third-party scripts. Some extensions inject extra elements into your page before React hydrates. If the error only happens on your machine and never for teammates, suspect this first and test in a clean browser profile.

Fix 1: Correct Invalid HTML Nesting

Look at this broken example. It looks harmless, and many beginners write exactly this:

// ❌ Wrong: a <div> and block content inside a <p>
<p>
  Welcome back!
  <div>Your last login was today.</div>
</p>
Enter fullscreen mode Exit fullscreen mode

The browser sees a div inside a paragraph, decides that cannot be right, and closes the paragraph early. Now the actual page structure is different from what React planned. The fix is to use valid nesting — a div as the outer element, with paragraphs inside it:

// ✅ Fixed: valid nesting
<div>
  <p>Welcome back!</p>
  <p>Your last login was today.</p>
</div>
Enter fullscreen mode Exit fullscreen mode

The same rule applies to tables, lists and headings. When in doubt, compare the rendered HTML in your browser tools with what your component wrote. A moved tag is a loud clue.

Next.js official documentation page for the React hydration error showing common causes and fixes

The official Next.js page for the hydration error — it lists invalid nesting and browser-only code as the top causes.

Fix 2: Move Browser-Only Code Into useEffect

If a value only exists in the browser, do not use it during the first render. Render a safe placeholder first, then fill in the real value after the component mounts, inside useEffect. Here is the classic localStorage mistake:

// ❌ Wrong: localStorage does not exist on the server
export default function Greeting() {
  const name = localStorage.getItem("name");
  return <p>Hello, {name}</p>;
}
Enter fullscreen mode Exit fullscreen mode

On the server this code cannot produce the same output as the browser, so a mismatch is guaranteed. Move the read into an effect, like this:

// ✅ Fixed: read browser data after mount
"use client";
import { useEffect, useState } from "react";

export default function Greeting() {
  const [name, setName] = useState("");
  useEffect(() => {
    setName(localStorage.getItem("name") || "");
  }, []);
  return <p>Hello, {name || "guest"}</p>;
}
Enter fullscreen mode Exit fullscreen mode

Now both sides say “guest” on the first render, so they match. A moment later the effect runs and the real name appears. This pattern — safe first render, real value after mount — solves a large share of hydration problems.

Fix 3: Make Dates and Random Values Stable

Anything that changes on every render is a risk. If you must show the current time, render it only after mount using the useEffect pattern above. If you need a random-looking ID, generate it on the server and pass it down as a prop, or use a stable ID from your data.

Screen-size conditions cause the same trouble: the server does not know the visitor's screen width. Use CSS to show and hide responsive parts instead of JavaScript conditions during the first render — CSS can differ by screen without changing the HTML structure.

A Quick Checklist to Find Your Culprit

Check What to look for
Read the error carefully React names the component and shows the differing text — start there, not everywhere
Validate your nesting No div, heading or list inside a paragraph; no button inside a button
Search for window and localStorage Any use outside useEffect or an event handler is a suspect
Search for Date and random Values rendered directly will differ between server and client
Test in a clean profile No extensions — if the error vanishes, an extension was injecting markup
Check third-party scripts Widgets that rewrite the page before hydration can also cause mismatches

Work through the list top to bottom, testing after each single change. React's own reference explains the comparison too: the hydrateRoot documentation on react.dev describes how React attaches to server HTML and why the first client render must match it.

What About suppressHydrationWarning?

suppressHydrationWarning tells React to ignore a mismatch on one specific element. It has one honest use: content that is supposed to differ, like a timestamp you fully control. It is not a general fix — slapping it everywhere hides real bugs. Use it rarely, on the smallest element possible, and only when you can explain why that element must differ.

Our comparison of React vs Next.js and their differences shows why plain React apps rarely meet this error while Next.js apps meet it early — server rendering is a Next.js feature, and hydration is its price of admission.

Frequently Asked Questions

Is a hydration error dangerous for my users?

Usually not dangerous, but not harmless either. React may throw away the server HTML and re-render everything in the browser, which makes the page slower and can cause flicker or lost form input. Fix it rather than ignoring it.

Why does the error appear only in development?

Development mode shows detailed warnings to help you debug. Production is quieter, but the mismatch can still cause slow or strange rendering, so a clean development console is the right goal.

Can a browser extension really cause this error?

Yes. Some extensions add wrappers, buttons or attributes to the page before React hydrates. Test in a private window or a fresh browser profile with no extensions before you blame your code.

Should I disable server-side rendering to avoid hydration errors?

No. That throws away the speed and SEO benefits you chose Next.js for. The causes are small and fixable — valid nesting, browser code inside effects, and stable values will cover almost every case.

Where should dynamic dates be rendered?

Either format a fixed date passed from the server, so both sides use the same value, or render the live time only after the component mounts in the browser.

Conclusion

A hydration error is simply React saying: “the server and I disagree.” Keep your HTML nesting valid, keep browser-only values out of the first render, and keep dates and random values stable, and the two sides will agree almost every time.

Top comments (0)