Next.js Error: hydration_failed — Hydration Failed
Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.
Warning: Text content did not match. Server: "12:04:07" Client: "12:04:08"
See more info here: https://nextjs.org/docs/messages/react-hydration-error
Hydration is the moment React takes over server-rendered HTML and “wires up” event handlers and state. A hydration error means the HTML the server sent and the HTML React expected to produce on the client don’t match. React can’t recover that mismatch in place, so it tears down the component subtree and re-renders it from scratch — losing the SSR benefit, causing a layout shift, and (in development) showing a red overlay.
The fix is almost never to suppress the warning. It’s to find the source of the mismatch, which is one of five things: time-dependent values, browser-only APIs, invalid HTML nesting, third-party DOM mutation, or user-specific data resolved differently on server and client. Each has a clean pattern.
Why this happens
- Time, random, or locale-dependent values rendered directly. `new Date().toLocaleString()`, `Math.random()`, `Intl.DateTimeFormat` with the user's locale, or `Date.now()` produce different output every render and across server/client. The server renders one value at request time; the client renders a different value milliseconds later.
- Browser-only APIs read during render. Reading `window`, `document`, `localStorage`, `navigator`, or `matchMedia` during render. On the server these are `undefined`, so the component renders one tree there and a different tree in the browser. Same for any code that branches on `typeof window !== 'undefined'`.
- Invalid HTML nesting. `<p><div></div></p>`, `<a><a></a></a>`, `<table>` without `<tbody>`, or `<button>` inside `<button>`. The browser silently rewrites the DOM to make it valid, then React's virtual DOM no longer matches the real DOM. Hydration trips because the structure changed before React got there.
- Third-party scripts mutating the DOM before hydration. Browser extensions (Grammarly, dark-mode injectors, password managers) and inline analytics scripts can add attributes (`data-gramm`, `cz-shortcut-listen`) or wrap nodes before React hydrates. The DOM React inherits no longer matches the server HTML.
- User-specific data fetched in a Client Component without SSR'd defaults. Authenticated user state from `localStorage`, theme preference from a cookie read client-side, or a feature-flag library that resolves on the client. The server renders the unauthenticated tree, the client immediately renders the authenticated one.
How to fix it
Fixes are ordered by likelihood. Start with the first one that matches your context.
1. Move dynamic values into useEffect (render once on client only)
For values that legitimately differ between server and client (current time, random IDs, user locale), render a stable placeholder during SSR and the dynamic value after hydration. This is the fix for ~70% of hydration errors.
'use client';
import { useEffect, useState } from 'react';
export function Clock() {
const [time, setTime] = useState<string | null>(null);
useEffect(() => {
setTime(new Date().toLocaleTimeString());
const id = setInterval(
() => setTime(new Date().toLocaleTimeString()),
1000,
);
return () => clearInterval(id);
}, []);
// Server + first client render both produce the same placeholder.
if (time === null) return <span suppressHydrationWarning>--:--:--</span>;
return <span>{time}</span>;
}
2. Disable SSR for components that genuinely can't render on the server
For components that depend on `window`, charting libraries that measure DOM, or third-party widgets that mutate the DOM, opt out of SSR entirely with `next/dynamic`. The server emits nothing for the component; the client renders it after mount.
import dynamic from 'next/dynamic';
const ClientOnlyChart = dynamic(
() => import('./Chart').then((m) => m.Chart),
{ ssr: false, loading: () => <div className="h-64" /> },
);
export default function Page() {
return <ClientOnlyChart />;
}
3. Fix invalid HTML nesting
Run the browser's Elements panel on the SSR'd HTML (View Source, not after hydration) and look for tags the browser silently moved. Common culprits: `<p>` containing block elements, `<a>` inside `<a>`, `<form>` inside `<form>`. Replace `<p>` with `<div>` when you need to nest block content; never wrap an interactive element inside another.
4. Use suppressHydrationWarning for known one-node mismatches
`suppressHydrationWarning` on a single element silences the warning for that one node only — useful for `<time>`, dark-mode `<html className>`, or text content you've intentionally made differ. Do NOT use it as a blanket fix; it just hides the symptom.
export default function RootLayout({ children }: { children: React.ReactNode }) {
// The theme script in <head> sets className before React hydrates.
return (
<html lang="en" suppressHydrationWarning>
<body>{children}</body>
</html>
);
}
5. Read cookies on the server, pass through props
For theme, locale, or user-specific data, read the cookie/header on the server (in a Server Component or in `getServerSideProps`) and pass it as a prop. Both the server and the first client render then see the same value, eliminating the mismatch.
Detection and monitoring in production
Hydration errors are loud in development (red overlay) but silent in production by default — the console logs a generic message and React falls back to a full re-render, costing TTI and CLS. Add `onRecoverableError` to your `hydrateRoot` (or use Sentry's `BrowserTracing` with React error boundaries) so production hydration mismatches show up in your error tracker. Track hydration error rate as a Core Web Vitals adjacent metric.
Related errors
- nextjsmodule_not_foundThe Next.js build (webpack/Turbopack) tried to resolve an import path and couldn't find it. Either the package isn't installed, the relative path is wrong, a TypeScript path alias isn't mirrored in `tsconfig.json` and `next.config.js`, or the file's case differs between disk and import (Linux is case-sensitive, macOS isn't).
- nextjsFUNCTION_INVOCATION_TIMEOUTA Vercel serverless function (a Next.js API route, server action, or `getServerSideProps`) didn't return a response within the plan's max execution time — 10s on Hobby, 60s on Pro, 900s on Enterprise. Vercel kills the invocation and returns 504.
- postgresECONNREFUSEDYour application tried to open a TCP connection to Postgres and the OS rejected it — Postgres isn't listening on the host:port you specified, or a firewall blocked the connection.
Frequently asked questions
Why does hydration only fail in production and not in development? +
Is `suppressHydrationWarning` a real fix? +
How do I find which component is causing the hydration mismatch? +
Does the App Router fix hydration errors automatically? +
Why does `<a>` inside `<a>` cause a hydration error when the browser still renders it? +
Can I use cookies/headers on the client to avoid the mismatch? +
Is hydration error a performance problem if it only happens once? +
Will turning off React strict mode hide the error? +
When to escalate to Next.js support
Hydration errors are virtually always in your code, not Next.js itself. Before opening a Next.js GitHub issue: (1) reproduce in a fresh Next.js app with the latest version, (2) confirm the issue persists with all browser extensions disabled and in incognito mode, (3) confirm the SSR'd HTML (curl the page) genuinely doesn't match the hydrated DOM. If all three hold, file at github.com/vercel/next.js with a minimal reproduction. Otherwise, the bug is upstream of Next.js (in your component, a third-party library, or a script you load).