This error occurs when Next.js server-side renders HTML that differs from what React produces during client-side hydration. Common causes include using browser APIs, dynamic dates/times, or non-deterministic values during the initial render. The fix involves ensuring consistent rendering between server and client.
Next.js uses server-side rendering (SSR) to generate HTML on the server and send it to the browser. When the page loads, React performs "hydration" - attaching event listeners and taking over the static HTML to make it interactive. A hydration mismatch happens when the HTML structure, text content, or attributes generated on the server do not match what React tries to render on the client. React expects the initial client render to produce identical output to the server HTML. When they differ, React cannot properly hydrate the page, leading to this error. The mismatch can be caused by environment differences (server vs browser), timing differences, or non-deterministic code that produces different results on each run.
Open browser DevTools (F12) and check the console for the exact error message. Next.js usually shows which component and element is mismatched. Look for messages like "Warning: Expected server HTML to contain a matching <div> in <div>" or "Text content did not match". The error often includes the actual server content versus expected client content.
# Example error output:
Warning: Text content did not match. Server: "Loading..." Client: "12/09/2025 3:45 PM"
in div (at Component.tsx:12)The most common cause is using browser-only APIs during render. Move any code accessing window, document, localStorage, or other browser APIs into a useEffect hook, which only runs on the client after hydration.
// WRONG - causes hydration mismatch
function WelcomeMessage() {
const username = localStorage.getItem('username');
return <div>Welcome {username}!</div>;
}
// CORRECT - server and client render the same initially
function WelcomeMessage() {
const [username, setUsername] = useState<string | null>(null);
useEffect(() => {
setUsername(localStorage.getItem('username'));
}, []);
return <div>Welcome {username || 'Guest'}!</div>;
}Date objects and timestamps generate different values on server versus client. Either format dates on the server and pass them as props, or defer date rendering until after hydration using useEffect.
// WRONG - server and client times will differ
function Timestamp() {
return <div>Page loaded at {new Date().toLocaleString()}</div>;
}
// CORRECT - use useEffect to update after hydration
function Timestamp() {
const [timestamp, setTimestamp] = useState<string>('')
useEffect(() => {
setTimestamp(new Date().toLocaleString());
}, []);
return <div>{timestamp ? `Page loaded at ${timestamp}` : 'Loading...'}</div>;
}
// ALTERNATIVE - pass formatted date from server
export async function getServerSideProps() {
return {
props: {
serverTime: new Date().toISOString()
}
};
}Browsers automatically fix invalid HTML nesting, which causes the actual DOM to differ from what React expects. Check that you are not nesting invalid elements.
// WRONG - div cannot be inside p
<p>
<div>Content</div>
</p>
// WRONG - button cannot be inside button
<button>
<button>Click me</button>
</button>
// CORRECT - use valid nesting
<div>
<div>Content</div>
</div>
// For styled text, use span inside p
<p>
<span className="highlight">Content</span>
</p>For specific elements where a mismatch is intentional and unavoidable (like showing server-generated timestamps), add suppressHydrationWarning to suppress the warning. Only use this as a last resort for non-critical content.
<div suppressHydrationWarning>
Rendered on server at {new Date().toISOString()}
</div>If a component cannot be made compatible with SSR, use Next.js dynamic imports with ssr: false to render it only on the client side.
import dynamic from 'next/dynamic';
// Component will only render on client, preventing hydration issues
const ClientOnlyComponent = dynamic(
() => import('../components/ClientOnlyComponent'),
{ ssr: false }
);
export default function Page() {
return (
<div>
<h1>My Page</h1>
<ClientOnlyComponent />
</div>
);
}Make sure both server and client use the same data for initial render. Use getServerSideProps, getStaticProps, or Server Components to fetch data once and pass it down.
// pages/posts/[id].tsx
export async function getServerSideProps({ params }) {
const post = await fetchPost(params.id);
return { props: { post } };
}
export default function Post({ post }) {
// Both server and client receive same post data
return <article>{post.content}</article>;
}Hydration issues may not appear in development mode. Build and run your app in production mode to catch these errors before deployment.
npm run build
npm run start
# Visit your app and check browser console for hydration errorsNext.js 13+ with the App Router provides better error messages for hydration mismatches compared to the Pages Router, often showing you the exact component and line number. In the App Router, Server Components do not hydrate at all - only Client Components (marked with "use client") undergo hydration. This architectural difference can help reduce hydration issues by moving server-only logic entirely to Server Components. For debugging difficult cases, use React DevTools browser extension to inspect component trees and compare server versus client renders. Some third-party libraries designed for browser-only use (charts, maps, rich text editors) may require wrapping in dynamic imports with ssr: false. Consider using the next/dynamic loading component with a custom loading placeholder to improve user experience while client-only components load. In rare cases, browser extensions like ad blockers or translation tools can modify the DOM before React hydrates, causing mismatches - test in incognito mode if issues persist.
Prop spreading could cause security issues
Prop spreading could cause security issues
Error: error:0308010C:digital envelope routines::unsupported
Error: error:0308010C:digital envelope routines::unsupported
React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render.
React Hook useEffect placed inside a condition
Hook can only be called inside the body of a function component
Hook can only be called inside the body of a function component
Rollup failed to resolve import during build
How to fix "Rollup failed to resolve import" in React