
Fix Next.js Hydration Errors in Production Without Guessing
A practical way to find the first server and browser difference behind a Next.js hydration error, with fixes for dates, browser APIs, HTML nesting, and extensions.
Hydration errors are annoying because the red message often points at the last component React touched, not the first thing that became different. The useful question is simpler: what did the server render, and what did the browser render on its first pass?
If those two trees are not the same, React has to stop and complain. Fix that first difference. Do not start by adding suppressHydrationWarning everywhere.
The short version
Look for values that can change between the server and browser: Date, Math.random, window, localStorage, screen size, browser extensions, and invalid HTML nesting. Move browser-only work into an effect, pass a stable value from the server, or isolate the component from SSR when that is really what you want.
export default function Clock() {
return <p>{new Date().toLocaleTimeString()}</p>
}That looks harmless, but the server clock and the browser clock are not guaranteed to match. Even a small difference is enough to produce a mismatch.
Start with the first mismatch, not the whole page
Make a production build and check the browser console from a clean session. Private browsing helps because extensions and saved state can change the result. Then remove half the page until the warning disappears. Add the pieces back until it returns.
This is boring, but it gives you a small reproduction. A small reproduction beats staring at a 1,000 line page component.
Browser APIs need to wait
window, document, localStorage, matchMedia, and navigator do not exist while the server is rendering. A typeof window check inside the returned markup can still produce two different trees, so it is not a complete fix.
'use client'
import { useEffect, useState } from 'react'
export function SavedTheme() {
const [theme, setTheme] = useState<string | null>(null)
useEffect(() => {
setTheme(localStorage.getItem('theme'))
}, [])
return <span>{theme ?? 'system'}</span>
}The first render is the same on both sides. The effect runs later, after hydration, when localStorage exists.
Dates and random values need a stable owner
For a timestamp, calculate it on the server and pass the value down. For a random ID, use React’s useId or create the value outside the render path. Do not use Math.random in JSX and hope the numbers line up.
import { useId } from 'react'
export function Field() {
const id = useId()
return (
<>
<label htmlFor={id}>Email</label>
<input id={id} name="email" />
</>
)
}HTML structure can also be the problem
Invalid nesting is easy to miss in a component tree. A paragraph inside another paragraph, a button inside a button, or an anchor inside an anchor can be repaired differently by the browser before React sees it.
Inspect the rendered HTML, not only the JSX. The browser inspector shows what the browser actually built.
Cloudflare, extensions, and other HTML changes
The Next.js docs also call out browser extensions and services that rewrite HTML. If the mismatch disappears in a clean browser but returns in your normal profile, test extensions before changing application code. Turn off HTML minifiers or response rewriting one at a time too.
A production checklist
Check these in order:
1. Reproduce with a production build and a clean browser.
2. Find the first element whose text or attributes differ.
3. Remove Date, random values, and browser APIs from the first render.
4. Validate HTML nesting around that element.
5. Check extensions, CDN transforms, and CSS-in-JS setup.
6. Use a client-only component only when the UI truly cannot render on the server.
fetch, secrets, databaseDefault. Keep work and dependencies on the server.useState, click, browser APIsAdd 'use client' only at the interactive edge.The goal is not to silence React. It is to make the first render deterministic, then add browser-only behavior after the page has hydrated.
Sources and further reading
- Text content does not match server-rendered HTML · Next.js
- hydrateRoot reference · React
- Next.js browser APIs guide · Next.js