Hydration failed: Text content does not match server-rendered HTML
Filed under Next.js & React errors
The fix, fast
Hydration fails when the HTML your server sends does not match what React expects to render in the browser, so React throws it away and re-renders. Find the exact spot from the error diff, then move anything that only exists in the browser — dates, random numbers, window values — into a state that fills in after mount with useEffect. Fix invalid nesting like a div inside a p, and test in an incognito window to rule out a browser extension changing the page.
Why this happens
In Next.js with the App Router, React first renders your page on the server and sends finished HTML to the browser. Then the browser loads JavaScript and React tries to attach interactivity to that HTML. That second step is called hydration — think of it as React checking its work against the paper copy the server already sent.
If the two copies disagree, React cannot trust the server copy. Sometimes that is your code rendering two different things: a current time, a random ID, or a value read from window or localStorage will be one thing on the server (or missing) and another in the browser. Sometimes the culprit is not your render at all. Browser extensions like password managers and grammar checkers quietly insert extra elements into the page before React checks it. Invalid HTML nesting causes the same problem, because the browser will rearrange tags like a <div> inside a <p>, or a button inside a button, into a shape React never sent.
- Client-only values during the first render:
new Date(),Math.random(),window.innerWidth, and anything fromdocument. - Invalid nesting: block elements inside
<p>, nested<a>or<button>tags, or table rows built in the wrong order. - Extensions: an extension injects a node, React sees an extra element, and the match fails only on your machine.
The fix, step by step
- Read the diff in the error, do not guess. Open your browser console and expand the hydration error. React highlights the mismatched text or element with a + / - style diff and points to the component stack. That location is almost always where the bug lives, so start there instead of rewriting the whole page.
// Console will name the component and show something like: // - Server: "10/14/2026, 9:41:02 AM" // + Client: "10/14/2026, 9:41:03 AM" - Test in an incognito window first. Open the same page in a private or incognito window with extensions disabled. If the error disappears, an extension is editing your page before React hydrates it. Your code is fine — note it and move on. If the error is still there, continue to the next step.
- Move client-only values to after mount. Do not render a date, random value, or browser-only reading during the first render. Start with empty or server-safe state, then fill it in inside
useEffect, which only runs in the browser after hydration. This keeps the first browser render identical to the server render."use client"; import { useEffect, useState } from "react"; export function TodayLabel() { const [label, setLabel] = useState(""); useEffect(() => { setLabel(new Date().toLocaleDateString()); }, []); return <p>Today is {label}</p>; } - Check for invalid HTML nesting. Look at the flagged component and its parents. Replace a
<p>that wraps cards or divs with a<div>, unwrap nested buttons and links so only one interactive element wraps the content, and make sure lists use<li>directly inside<ul>or<ol>. The browser silently fixes bad nesting, and that silent fix is the mismatch.// Breaks: browser will close the p early // <p><div>Card content</div></p> // Works: <div><div>Card content</div></div> - Guard direct window and document access. Any code that touches
window,document, orlocalStorageduring render will differ on the server, where those objects do not exist. Move that read intouseEffectas in step 3, or render a stable placeholder first and swap it once mounted. Never make the server and client return completely different trees if you can avoid it — a placeholder that fills in is safer. - Use suppressHydrationWarning only for one known-safe spot. If a single value truly cannot match — a live timestamp is the classic example — you can add
suppressHydrationWarningto that one element. This tells React to ignore differences inside that element only. It is not a blanket fix: putting it on a large container hides real bugs, does not fix invalid nesting elsewhere, and will leave you blind the next time something actually breaks.<time suppressHydrationWarning> {new Date().toLocaleTimeString()} </time>
Give your AI this prompt
I am getting this Next.js App Router error: "Hydration failed because the initial UI does not match what was rendered on the server" / "Text content does not match server-rendered HTML". Here is the full console error and the component it points to: [paste error and component code]. Please find the exact mismatch. Check for: 1) values rendered during the first render that differ between server and browser such as Date, Math.random, window, document, or localStorage, 2) invalid HTML nesting such as div inside p or nested button/a tags, 3) conditional rendering that returns different markup on server vs client. Fix it by moving client-only values into useState + useEffect so the first render matches the server, or by fixing the nesting. Only use suppressHydrationWarning if it is a single known-safe element like a timestamp, and explain why. Constraints: make one change at a time, do not rewrite unrelated files, do not change my layout or styling, and tell me which cause you found before you change code.