Hydration errors in Next.js: causes, fixes and whether they hurt SEO
A Next.js hydration error means server HTML and the first client render differ, so React rebuilds the tree. The causes, the fix for each, and the SEO cost.
LLaunchScaler·Published ·10 min read
A hydration error in Next.js means the HTML your server sent does not match what React renders on its first pass in the browser, so React discards that server HTML and regenerates the tree on the client. The fix is to make the first client render identical to the server render: move anything that depends on the browser, the clock or randomness into useEffect, correct invalid HTML nesting, and reserve suppressHydrationWarning for single values such as a timestamp.
The error rarely costs you rankings directly. It costs you a flash of changed content, a slower start and, on a landing page, clicks that land before React has attached any handlers. Below are the causes in the order they usually turn up, the fix for each, and how to confirm the fix held.
What is a hydration error in Next.js?
Hydration is the step where React takes the HTML the server prerendered and makes it interactive by attaching event handlers. A hydration error means React's first render in the browser produced a different tree from the one in that HTML. React 19 then regenerates the affected tree on the client instead of reusing the server markup.
In React 19 the console 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." Older Next.js projects show the wording that people still search for, "Text content does not match server-rendered HTML", which is also the title of the Next.js error page. Both describe the same event.
React's own list of what triggers it, printed inside that message, covers a server/client branch such as if (typeof window !== 'undefined'), variable input such as Date.now() or Math.random(), date formatting in the user's locale, external data that changed without a snapshot sent along with the HTML, invalid tag nesting, and a browser extension changing the HTML before React loaded.
What causes hydration errors?
Almost every hydration error comes from one of seven sources: invalid nesting, a browser-only check or API in render, a time or random value in render, a locale-dependent format, a browser extension, a misconfigured CSS-in-JS library, or a CDN that rewrites your HTML. The table maps each to what it looks like in code and the fix.
Questions, answered
What people ask about this
01
What is a hydration error in Next.js?
It is the error React raises when the HTML the server sent does not match what the component tree renders on its first pass in the browser. React 19 reports it as 'Hydration failed because the server rendered HTML didn't match the client' and regenerates that tree on the client.
A <div> or <ul> inside a <p>, a <p> inside a <p>, an <a> inside an <a>, a <button> inside a <button>
Change the outer element (a <p> holding blocks becomes a <div>), or restructure so interactive elements are siblings
Browser check in render
typeof window !== 'undefined' ? A : B inside the component body
Render the server branch first, then switch in useEffect
Browser-only API in render
Reading localStorage, window.matchMedia or window.innerWidth to pick a theme or layout
Read it in useEffect after mount, or use CSS media queries for layout
Time or random value in render
new Date(), Date.now(), Math.random() used for text or IDs
Pass the value from the server as a prop, use useId for IDs, or update after mount
Locale or time zone formatting
toLocaleDateString() with no arguments renders in the server's locale and zone, then in the visitor's
Pass an explicit locale and timeZone so both sides format the same string, or format after mount
Browser extension
A password manager, translator or grammar tool injects nodes before React hydrates
Reproduce in a clean profile before changing code; nothing to fix if it disappears
CSS-in-JS set up wrong
Class names or style tags differ between server and client
Follow the library's official Next.js example, including its style registry
CDN rewriting HTML
An edge feature minifies or changes the response, such as Cloudflare's Auto Minify
Turn off HTML rewriting for the site
Two sources catch people who have already checked the obvious ones. iOS Safari turns phone numbers, email addresses and addresses in text into links, which changes the DOM before hydration; Next.js recommends <meta name="format-detection" content="telephone=no, date=no, email=no, address=no">, and in the App Router the formatDetection field of the metadata export generates it. And an incrementing counter used for element IDs can mismatch because Client Components may hydrate in a different order from the one the server emitted.
How do you find the element that mismatched?
Read the diff React prints, then confirm by comparing the raw server HTML with the rendered DOM. React 19 logs a single message with a diff of the mismatch, and Next.js shows it in the development overlay. Work through these steps before you change code.
Run next dev and load the page. Since Next.js 16.2 the error overlay labels the diff with a + Client / - Server legend, so you can read which value each side rendered and which component rendered it.
Open the page in an incognito window with every extension disabled. If the error disappears, an extension caused it and your code is fine.
In Chrome DevTools, open the Command Menu (Command+Shift+P on macOS, Control+Shift+P on Windows and Linux), run Disable JavaScript and reload. It stays off in that tab while DevTools is open. What you now see is the server HTML alone. Re-enable JavaScript and compare the two versions of the element the diff named.
Search that component for the patterns in the table: Date, Math.random, window, localStorage, typeof window, toLocale. Check its JSX for a <p> wrapping block elements or components that render them.
Run a production build (next build then next start) and test again, because some mismatches only appear with production data or caching.
If the diff points at text inside <html> or <body> that none of your components render, check the CDN first. React 19 skips over unexpected tags that third-party scripts and extensions insert in <head> and <body>, so an error that survives an extension-free profile points back at your own markup or at something rewriting the response.
How do you fix a hydration error in Next.js?
Make the first render in the browser produce exactly what the server produced, then change it afterwards. Next.js documents three ways to do that: render the server value first and update it in useEffect, skip server rendering for one component with next/dynamic and ssr: false, or add suppressHydrationWarning to a single element whose text must differ.
Move browser-only values into useEffect
This is the fix for most cases. Render the value both sides agree on, then set the browser-only value after mount:
The server and the first client render both print "Welcome", so hydration matches, and the effect updates the text afterwards. React's documentation notes the cost: the component renders twice, which makes hydration slower, and a visible change right after load can feel jarring on a slow connection. Keep the server version close to the final one.
Use suppressHydrationWarning for one timestamp
When a value must differ, such as "posted 3 minutes ago", add suppressHydrationWarning to that element. It works one level deep only, and React will not patch the mismatched text, so the server's text stays until your code updates it. Next.js calls it an escape hatch and says not to overuse it. Putting it on <body> to hide every warning also hides the next real bug.
Skip server rendering for one component
For a widget that cannot render on a server at all, such as a chart that measures the window, import it with next/dynamic:
ssr: false only works inside a Client Component. Next.js throws an error if you use it in a Server Component, so put the import in a file that starts with 'use client'. The trade-off is that the component is absent from the server HTML, so crawlers that do not run JavaScript never see its content. Use it for interactive widgets, never for your headline, pricing or body copy.
Replace random IDs with useId
If you generate IDs for aria-describedby or htmlFor with Math.random() or a counter, switch to useId. React derives the ID from the component's position in the tree, so it matches between server and client regardless of hydration order. Do not use it for list keys; React says keys should come from your data.
Fix the nesting
A <div> inside a <p> is invalid HTML, so the browser's parser closes the <p> early and the DOM React finds is not the tree it rendered. Change the outer element to a <div>. Watch for components that return block elements and are dropped inside a paragraph, such as a card, an image wrapper or a list.
Do hydration errors hurt SEO?
The direct SEO cost is usually small, because crawlers receive your server HTML before any hydration happens. Crawlers that run no JavaScript index that HTML as it is, and Google renders the page in headless Chromium and indexes the rendered result. The real cost is what visitors experience while React regenerates the tree.
Here is what each reader of the page gets:
Who loads the page
What they see
Effect of a hydration mismatch
Googlebot
The server HTML, then the page rendered in headless Chromium; Google indexes the rendered HTML
Little, unless the client version drops or changes content Google would have indexed
AI crawlers from OpenAI and Anthropic
The server HTML only; Vercel's 2024 study found none of the major AI crawlers render JavaScript
None from the mismatch itself; they never run the client code
A visitor
The server HTML, then the regenerated tree
A flash of changed content, a slower start, and controls that are inert until handlers attach
Two situations turn a UX problem into a search problem. If you "fix" the error with ssr: false on a component that holds real content, that content leaves the server HTML, and every crawler that does not render JavaScript loses it. And if a component throws while rendering on the client, React shows the closest error boundary, so a broken client render can replace content that the server HTML did contain. Check both with Search Console's URL Inspection tool: Google's JavaScript SEO guide says to look at the rendered HTML there to confirm your content appears after rendering.
For visitors the cost is concrete. Server HTML for a button carries no click handler, and hydration is the step that attaches one. While React regenerates a mismatched tree, a signup button can look ready and do nothing. The extra render also runs on the main thread during load, which delays the page's response to the first tap; the guide to improving Interaction to Next Paint covers how to measure that delay.
How do you stop hydration errors coming back?
Treat a hydration warning as a failing test, not console noise. The patterns that cause it creep back each time someone adds a date, a theme toggle or a personalised greeting, and the development overlay only helps the developer who happens to load that page.
Add a lint rule or code review note for Date, Math.random, window and localStorage inside the render body of Client Components.
Run your key pages in a headless browser in CI and fail the build on any console error whose text includes "Hydration failed". Playwright's page.on('console') listener hands you each message, and msg.type() === 'error' filters to errors.
Test the homepage, pricing and signup pages in a production build, in a clean browser profile, after each release.
Click every primary button after a fix, with the mouse and with the keyboard, using the pre-launch CTA test. A hydration failure upstream is one of the ways a button ends up doing nothing.
Check your live pages for hydration errors
Your development overlay shows mismatches on the pages you open. To check what a first-time visitor's browser hits on your live site, run the free scan first, then open the full audit. The free LaunchScaler scan needs only your URL and no account, runs 156 checks across 6 of its 7 categories, and loads the page in a real browser for its does-it-work checks, one of which is "Hydration mismatch between server HTML and client render". Beside it sit the checks for a primary CTA that does nothing, unhandled promise rejections and console errors along the conversion path.
The free run shows each check's verdict. The full audit, $19 one time for your domain and never expiring, opens every check to its evidence and its exact fix, so you see which elements mismatched and what to change in the component before you ship the next release.
How do I fix 'Text content does not match server-rendered HTML'?
Find the value that differs between server and browser, usually a date, a random number, a window or localStorage read, or invalid nesting such as a div inside a p. Render the same value on both sides, then change it after mount in useEffect, or skip server rendering for that one component with next/dynamic and ssr: false.
03
Do hydration errors affect SEO?
Usually only a little. Every crawler receives the server HTML, crawlers that do not run JavaScript index exactly that, and Google renders the page and indexes the result. The larger cost is to users: a content flash, a second render and controls that do nothing until React finishes.
04
Is suppressHydrationWarning safe to use?
For one element whose text must differ, such as a timestamp, yes. It only works one level deep, React will not patch the mismatched text, and Next.js calls it an escape hatch not to overuse.
05
Can a browser extension cause a hydration error?
Yes. Next.js lists extensions that modify the HTML as a cause. Reproduce the page in an incognito window with extensions disabled before you change any code.
Split LCP into four sub-parts, compare each to its budget and fix the slow one: server time, a late or lazy hero image, a heavy file or blocked rendering.