This React hydration error occurs when the HTML pre-rendered on the server differs from what React renders on the client during hydration. Common causes include timestamp differences, browser-only API usage, and inconsistent conditional rendering between server and client.
This error appears during React's hydration process, which occurs when React attempts to attach event handlers to server-rendered HTML. Hydration is React's way of converting static HTML (sent from the server) into an interactive application by binding JavaScript event handlers and state management to the DOM. When React hydrates the page, it expects the server-rendered HTML to exactly match what would be rendered on the client. If React detects any differences in the text content, DOM structure, or attributes, it throws this hydration error. The mismatch tells React that something rendered differently on the server versus the client, which breaks the assumptions hydration relies on. This error is particularly common in server-side rendered (SSR) applications using frameworks like Next.js, Gatsby, or custom React SSR setups. While React will attempt to recover by re-rendering the mismatched parts, this recovery process is slower and can cause visual flicker or unexpected behavior.
React 18+ provides detailed hydration error messages showing the component tree and exact content mismatch. Check your console for messages like:
Warning: Text content did not match. Server: "12:34 PM" Client: "12:35 PM"
at span
at TimeDisplayThe error will show you the component name and the actual text difference. If you're using an older React version, add more console.logs to narrow down which component is causing the issue.
The most common fix is to move any browser API access or time-dependent code into useEffect, which only runs on the client after hydration:
import { useState, useEffect } from 'react';
function TimeDisplay() {
const [mounted, setMounted] = useState(false);
const [currentTime, setCurrentTime] = useState('');
useEffect(() => {
setMounted(true);
setCurrentTime(new Date().toLocaleTimeString());
}, []);
// Show nothing or a placeholder during SSR
if (!mounted) {
return <span>Loading...</span>;
}
return <span>{currentTime}</span>;
}This ensures the server renders consistent content that matches the initial client render, then updates after hydration completes.
For components that need browser APIs but must render immediately, use a two-pass approach:
import { useState, useEffect } from 'react';
function ThemeToggle() {
const [theme, setTheme] = useState<string | null>(null);
useEffect(() => {
// Second pass: get actual theme from localStorage
const savedTheme = localStorage.getItem('theme') || 'light';
setTheme(savedTheme);
}, []);
// First pass: render neutral/default state
if (theme === null) {
return <div className="theme-light">Default Theme</div>;
}
return <div className={`theme-${theme}`}>Themed Content</div>;
}The initial render (both server and first client render) will show the default state, preventing mismatches. The useEffect then updates to the correct value.
Browsers automatically fix invalid HTML, but the fixes may differ between server and client. Common issues:
// ❌ WRONG: div inside p is invalid HTML
<p>
<div>Content</div>
</p>
// ✅ CORRECT: Use span or div as parent
<div>
<div>Content</div>
</div>
// ❌ WRONG: Nested p tags
<p>
Some text
<p>Nested paragraph</p>
</p>
// ✅ CORRECT: Use separate blocks
<div>
<p>Some text</p>
<p>Second paragraph</p>
</div>Validate your HTML structure using browser DevTools or an HTML validator to catch these issues.
If a component absolutely must use browser APIs and can't work with server rendering, disable SSR for that component:
Next.js:
import dynamic from 'next/dynamic';
const ClientOnlyComponent = dynamic(
() => import('./ClientOnlyComponent'),
{ ssr: false }
);
function Page() {
return (
<div>
<h1>My Page</h1>
<ClientOnlyComponent />
</div>
);
}React with lazy loading:
import { lazy, Suspense, useState, useEffect } from 'react';
function ClientOnlyWrapper({ children }: { children: React.ReactNode }) {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
return mounted ? <>{children}</> : null;
}
const MapComponent = lazy(() => import('./MapComponent'));
function Page() {
return (
<ClientOnlyWrapper>
<Suspense fallback={<div>Loading map...</div>}>
<MapComponent />
</Suspense>
</ClientOnlyWrapper>
);
}For cases where a mismatch is intentional and unavoidable (like timestamps), suppress the warning:
function ServerTimestamp() {
return (
<time suppressHydrationWarning>
{new Date().toISOString()}
</time>
);
}Important caveats:
- Only use this as a last resort after trying other solutions
- Only works on the direct element, not children (one level deep)
- Does NOT fix the underlying issue, just hides the warning
- React will still use the client-rendered content, causing a brief flicker
- Don't use this to mask bugs; fix the root cause when possible
Browser extensions (ad blockers, password managers, React DevTools) and third-party scripts can inject HTML before React hydrates:
Testing approach:
1. Test in incognito/private mode with all extensions disabled
2. If the error disappears, a browser extension is the cause
3. Re-enable extensions one by one to identify the culprit
Protecting against extension modifications:
// Add a wrapper that React controls completely
function ProtectedContent() {
return (
<div suppressHydrationWarning>
<div>
{/* Your actual content here */}
Content that extensions might modify
</div>
</div>
);
}For production apps, you may need to accept that some users will have extensions that cause hydration mismatches. Use suppressHydrationWarning strategically on affected containers.
Understanding React's hydration process: React hydration works by traversing the server-rendered DOM tree and the virtual DOM tree simultaneously. When text content, attributes, or structure don't match, React must discard the server HTML and re-render from scratch, which is expensive and defeats the purpose of SSR.
React 18 improvements: React 18 introduced selective hydration and streaming SSR, which handle hydration mismatches more gracefully. Components can hydrate independently, and priority hydration ensures interactive elements hydrate first. However, mismatches still cause performance issues and warnings.
Debugging with React DevTools: Install React DevTools browser extension and enable "Highlight updates when components render" to visually see which components re-render during hydration. This helps identify problematic components.
Production considerations: In production, hydration errors can impact Core Web Vitals, specifically Cumulative Layout Shift (CLS) and First Input Delay (FID). The re-rendering causes layout shifts and delays interactivity.
Testing for hydration issues: Always test SSR applications with JavaScript disabled first, then enable it to watch the hydration process. Use tools like Lighthouse and WebPageTest to measure the impact of hydration mismatches on performance metrics.
Framework-specific considerations:
- Next.js: Provides detailed hydration error messages in development. Use next/dynamic with ssr: false for client-only components.
- Gatsby: Uses static generation by default, so hydration issues are less common but can occur with browser APIs.
- Remix: Handles hydration differently with progressive enhancement; fewer hydration issues by design.
Common timestamp patterns: For displaying relative times (like "5 minutes ago"), always use the server timestamp as the source of truth and calculate differences on the client after hydration, not during render.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite