Error: Hydration failed because the initial UI does not match what was rendered on the server (Next.js)
Diagnoses and fixes the Next.js hydration error "Hydration failed because the initial UI does not match what was rendered on the server" (React 18+), plus the "Text content did not match server-rendered HTML" warning variant. Use when a Next.js app logs hydration errors or warnings in the console, flashes wrong content on load, or behaves oddly right after the first render. Not for build errors, routing errors, TypeScript errors, or visual bugs with no hydration warning.
TL;DR: The server-rendered HTML and the first browser render disagree. Find the differing element in the React console error (it names the component and the mismatched attribute or text), then make that piece deterministic on the server: move browser-only values into useEffect, render client-only content only after mount, or load the component with next/dynamic and ssr disabled.
Verbatim error (React 18+):
Error: Hydration failed because the initial UI does not match what was rendered on the server.Steps
1. Reproduce and read the full console error
Open the page in a fresh incognito window with the dev console open and reload. React 18+ logs the component stack and names the exact attribute or text node that differed (for example "Warning: Text content did not match. Server: \u201c10:00\u201d Client: \u201c10:05\u201d"), plus the component that rendered it.
Expected output: the console names one specific component and the mismatched value, for example:
Warning: Text content did not match. Server: "12:00 PM" Client: "12:05 PM"
at time
at FormattedTimeSuccess check: you can point at a single line of code whose output differs between server and client.
2. Find the non-deterministic render value
In the named component, look for any of these computed during render (not inside useEffect or an event handler):
new Date(),Date.now(), ortoLocaleString()formatting (server and client are in different timezones or render at different times)Math.random()or generated IDswindow,document,localStorage,navigator, ormatchMediaread during render- content that depends on the viewport or theme before the client has mounted
- invalid HTML nesting such as a div inside a p, or a button inside a button (the browser rewrites the DOM, so hydration sees different markup)
Success check: you have one expression that provably returns different values on the server vs the client.
3. Make the server render deterministic
Pick the fix that matches the cause:
Browser-only values (dates, viewport, storage): initialize state to a placeholder and fill it in useEffect, which only runs in the browser:
'use client';
import { useState, useEffect } from 'react';
export function FormattedTime() {
const [time, setTime] = useState(null); // a string once mounted
useEffect(() => {
setTime(new Date().toLocaleTimeString());
}, []);
if (time === null) return '--:--';
return time;
}Entire component is client-only (maps, editors, charts): skip server rendering with next/dynamic:
import dynamic from 'next/dynamic';
const Map = dynamic(() => import('./Map'), { ssr: false });Value is intentionally different and harmless (for example a theme class on the html tag): add suppressHydrationWarning on that one element. Use this sparingly; it silences the check rather than fixing the cause.
Success check: server HTML and first client render produce identical output.
4. Verify the error is gone
Hard-refresh (Cmd/Ctrl+Shift+R) in an incognito window and watch the console. The hydration error and warnings should be gone.
Expected output: no hydration warnings in the console, and the page renders identically before and after JS loads.
When this applies
- Console shows "Hydration failed because the initial UI does not match what was rendered on the server"
- Console shows "Text content did not match" / "did not match server-rendered HTML"
- A Next.js page renders fine but flashes wrong content on load, or interactive elements behave oddly after hydration
- The error appears only in production or on the first load, not during client-side navigation
When this does not apply
- Build-time errors, module resolution errors, or TypeScript errors: those fail before any HTML is produced
- Routing errors (404s, redirect loops): no server/client markup comparison is involved
- Styling issues that render identically on server and client: a visual bug with no console hydration warning is not this error
- The error names a third-party script tag that modifies the DOM: see edge cases below
Variant phrasings
Warning: Text content did not match. Server: "..." Client: "..."
The older React 17 wording and the React 18 companion warning for the same root cause. Same fixes as above; this variant usually points at a date, time, or locale-formatted string.
Warning: Prop className did not match
Usually a theme or viewport-dependent class computed during render (dark mode toggles, responsive class names). Mount-gate the class or move it to useEffect.
Hydration mismatch only in the App Router
Server Components render on the server by default; any browser API used inside them is a hydration mismatch at best and a runtime crash at worst. Mark the component 'use client' and apply the same deferred-render patterns. next/dynamic with ssr: false only works inside Client Components.
Why it happens
Next.js renders the page to HTML on the server for speed and SEO. In the browser, React then "hydrates" that HTML: it renders the component tree again and expects the output to match the server HTML exactly before attaching event listeners. Anything non-deterministic (time, randomness, browser-only APIs, timezone-dependent formatting, invalid nesting the browser rewrites) breaks that equality, and React throws instead of silently adopting possibly wrong UI.
Edge cases
- Browser extensions (grammar checkers, password managers, ad blockers) modify the DOM before React hydrates, causing a mismatch you cannot reproduce in incognito. Test in a clean profile first.
- Third-party widgets and embeds that inject their own DOM (chat bubbles, review stars) should be loaded inside useEffect or with ssr disabled, never as server-rendered JSX around their mount point.
suppressHydrationWarningon a parent does not silence mismatches inside children in all React versions; put it on the exact element that differs.- Dates formatted with toLocaleString differ by the user's locale and timezone, not just server vs client. Normalize with an explicit locale and timeZone (for example
toLocaleString('en-US', { timeZone: 'UTC' })) when the value must be stable. - In React 19, hydration warnings are errors by default in more cases; the same fixes apply, but you can no longer ignore the warning and ship.
- CSS-in-JS libraries that inject style tags in different order on server vs client can trigger attribute mismatches; check the library's Next.js SSR setup docs.
- Invalid HTML nesting (div inside p, li outside ul) is silently rewritten by the browser's HTML parser before React hydrates, so the mismatch looks like React's fault. Validate nesting first.
Compatibility
Next.js 12 (Pages Router), 13, 14, 15, 16 (App Router and Pages Router); React 18 and 19. The error string above is React 18+; React 17 logs the "Text content did not match" warning variant.
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.