glimfly
code updated

What is a hydration error in a Next.js or React app?

In one sentence:A hydration error is React's warning that the interactive version it just built in the browser doesn't match the plain HTML the server sent down, usually because some value rendered differently on each side.

What it actually is

Most apps your agent builds render each page twice. Once on the server (or at build time), producing plain HTML that reaches the browser so something shows up immediately. Once again in the browser, where React attaches the real interactivity, click handlers, state, event listeners, to that same markup. That second pass is called hydration: React wakes static HTML into a live component tree, and expects both passes to match exactly. When they don’t, it logs a hydration error and throws away the piece it can’t trust, then rebuilds it fresh in the browser.

Picture the server-rendered HTML as a stage set built ahead of time, in the dark, before the audience arrives. Hydration is the crew walking onto that same stage to wire the lights and props to what’s already sitting there. If a chair moved an inch between the build and the crew’s arrival, they can’t wire a light to an empty spot. They notice the mismatch, tear that piece out, and rebuild it live, in front of everyone. The show goes on. It just stutters for a moment.

Why your AI just did this

You’ll meet this almost entirely in apps using server-side rendering, the default for anything scaffolded with create-next-app, and what Lovable, Bolt, and v0 build on top of. Three patterns cause nearly every case an agent introduces on its own:

  • A value that changes every time it runs: Date.now(), Math.random(), or a “time ago” label computed during render instead of after the page loads
  • A browser-only read happening during render instead of inside useEffect: window, localStorage, or navigator.language. The server has no browser, so it renders a placeholder, and the two sides disagree the moment the real value shows up
  • Invalid HTML nesting the agent copied into a layout: a <div> inside a <p>, or a <button> inside an <a>. The browser silently corrects the tag soup before React ever sees it, so React’s tree stops matching the DOM

On top of what your agent writes, a browser extension can inject attributes into your page before React hydrates it, which Next.js documents as a known cause, citing a real report on React’s own issue tracker. Grammarly and password managers are common examples: they add their own attributes to your <body> tag, and React flags them as unexpected.

When you’ll run into it

In React 19, the message reads: “Hydration failed because the server rendered HTML didn’t match the client. As a result this tree will be regenerated on the client.” React 18 phrased it differently: “Hydration failed because the initial UI does not match what was rendered on the server.” Same meaning, different version of the wording.

Locally, you’ll usually see a more detailed warning first, titled “Text content does not match server-rendered HTML” in Next.js’s own docs, often naming the specific mismatched value right in your browser console (F12, Console tab). If a browser extension is the cause, the warning often lists “extra attributes from the server” tracing back to that extension, not your code.

The telltale symptom is a flash: the page loads one way, then a piece swaps a moment later, sometimes with a visible flicker. This differs from a crash an error boundary catches. A hydration mismatch is logged and usually self-heals without taking down the rest of the page.

What to check

  • Read the console message fully before guessing. In development, React names the exact element and often the exact value that differed
  • Search the flagged component for anything computed fresh on each render: dates, random numbers, locale formatting. Move it into useEffect or compute it once and store it
  • Look for window, document, localStorage, or navigator reads sitting directly in the render body instead of behind useEffect or a “mounted” check
  • Compare the live DOM in your browser’s Elements tab to your JSX, especially around <p>, <a>, and <button> tags nested inside each other
  • Reproduce in an incognito window with extensions disabled. If the error disappears, it’s an extension, and suppressHydrationWarning on that element is the right tool
  • Treat this as a signal worth understanding: it means part of your render logic isn’t deterministic, even if the page still works

Say it like a dev

Instead of: “my app shows a weird error about hydration” Say: “I’m getting a hydration mismatch naming [element/value]. Find what renders differently on the server versus the client and fix the source instead of suppressing the warning.”

Instead of: “just make the hydration error go away” Say: “Don’t suppress the warning. Check whether it’s a Date or random value, a window read during render, or invalid HTML nesting, and fix that specific cause.”

Instead of: “it worked yesterday and now there’s a hydration error” Say: “Test this in an incognito window with extensions off first, to rule out a browser extension before touching any code.”

People actually ask

“Why do I keep getting a hydration error in my Next.js app?”

Something in your app renders one way on the server and a different way the moment React takes over in the browser. The usual culprits your agent introduces are a value like Date.now() or Math.random() computed during render, a window or localStorage read outside useEffect, or invalid HTML nesting like a div inside a p tag. React notices the mismatch, logs the error, and rebuilds that piece of the page from scratch in the browser.

“Is a hydration error actually breaking my app?”

Usually not in a way you'll see. React recovers by discarding the mismatched piece and re-rendering it on the client, so the page still works and the fix is mostly about tidying up code that isn't deterministic. It's worth fixing anyway: in rarer cases a whole section of the page switches to client rendering, which resets any state or animation it was holding.

“My console names a browser extension, is that really causing this?”

Yes, that's a documented cause, not a bug in your code. Extensions like Grammarly or password managers can inject attributes into your page's body or head tag before React hydrates, and React reads those extra attributes as a mismatch. Test in an incognito window with extensions off to confirm, then it's safe to ignore or suppress for that one element.

Related terms

Go deeper

Checked against

free tool · no signup

Just met this in a real session? Glim reads it in plain words.

Paste what your agent just did, a git diff, your terminal, or its summary, and Glim tells you what changed and what to check before you ship. Nothing stored.

Explain my session →