The React DOM hydration error means the HTML sent from the server doesn’t exactly match what React expected to render on the client.
This mismatch usually happens because the server and client disagree on the initial content of the DOM.
Common Causes and Fixes
1. Dynamic Content Rendered on Client Only
-
Diagnosis: Check your components for any
useEffecthooks oruseStatecalls that directly manipulate the DOM or set initial state based on browser APIs (likewindow.innerWidth,localStorage,navigator). Compare the initial render output on the server (view source in your browser) with the client-rendered DOM. -
Fix: Move client-only logic into
useEffector use a conditional render based on atypeof window !== 'undefined'check. For example, if you’re setting a class based on screen size:import React, { useState, useEffect } from 'react'; function MyComponent() { const [isMobile, setIsMobile] = useState(false); useEffect(() => { const handleResize = () => setIsMobile(window.innerWidth < 768); window.addEventListener('resize', handleResize); handleResize(); // Initial check return () => window.removeEventListener('resize', handleResize); }, []); // Render differently on server vs client if needed if (typeof window === 'undefined') { return <div className="desktop-view">Loading...</div>; // Server render fallback } return <div className={isMobile ? 'mobile-view' : 'desktop-view'}>Content</div>; } -
Why it works: The server renders a static fallback, and
useEffectruns only on the client after the initial render, allowing React to reconcile the DOM with the correct, client-determined state.
2. Mismatched dangerouslySetInnerHTML Content
-
Diagnosis: If you’re using
dangerouslySetInnerHTML, ensure the HTML string being injected is identical on both server and client. This often occurs when the HTML string is dynamically generated or fetched. -
Fix: Ensure the source of the HTML is static or that any dynamic parts are generated identically on both server and client. If the HTML comes from an API, fetch it on the client after the initial render or ensure your server-side rendering (SSR) setup can fetch and process it identically.
// BAD: HTML might differ between server and client <div dangerouslySetInnerHTML={{ __html: getServerGeneratedHTML() }} /> // GOOD: If HTML is static or fetched identically function MyComponent({ staticHtml }) { return <div dangerouslySetInnerHTML={{ __html: staticHtml }} />; } -
Why it works:
dangerouslySetInnerHTMLbypasses React’s diffing for its children, so the raw DOM nodes must match perfectly.
3. Differences in Component Rendering Logic
-
Diagnosis: Components that render different output based on environment-specific variables (e.g., feature flags loaded differently on server vs. client, or differences in
navigator.userAgent). -
Fix: Use a universal check like
typeof window !== 'undefined'to ensure the component renders the same structure on the server as it will on the client before any client-specific logic runs.function FeatureComponent({ featureEnabled }) { if (typeof window === 'undefined') { return null; // Or a placeholder div } if (!featureEnabled) { return <div>Feature disabled</div>; } return <div>Feature enabled!</div>; } -
Why it works: This ensures the server renders a predictable, identical structure that React on the client can then update if
featureEnabledchanges or ifwindowbecomes available.
4. External Libraries Modifying DOM
-
Diagnosis: Libraries that directly manipulate the DOM outside of React’s control (e.g., some charting libraries, older jQuery plugins) can interfere. Check your
useEffecthooks for direct DOM manipulation calls. -
Fix: Ensure these libraries only run on the client side, wrapped in
useEffect.import React, { useEffect } from 'react'; import MyChartLib from 'my-chart-lib'; function ChartWrapper({ data }) { useEffect(() => { if (typeof window !== 'undefined') { const chart = new MyChartLib(document.getElementById('chart-container'), data); return () => chart.destroy(); // Cleanup } }, [data]); // Re-run if data changes if (typeof window === 'undefined') { return <div id="chart-container">Loading chart...</div>; // Server render placeholder } return <div id="chart-container" />; } -
Why it works: The server renders a placeholder, and the DOM-modifying library is initialized only on the client once the DOM is ready.
5. Missing or Incorrect key Props in Lists
-
Diagnosis: If you have lists of elements and the
keyprop is missing, unstable, or identical for multiple items, React might incorrectly match elements between server and client renders, especially if the list order or content changes slightly. -
Fix: Always provide stable, unique
keyprops for list items. Prefer unique IDs from your data over array indices.// BAD: Using index as key can cause hydration issues if list order changes {items.map((item, index) => <li key={index}>{item.name}</li>)} // GOOD: Use a stable ID {items.map(item => <li key={item.id}>{item.name}</li>)} -
Why it works: Stable keys allow React to correctly identify, add, remove, or update individual elements in a list, ensuring consistency between server and client DOM trees.
6. Incorrect suppressHydrationWarning Usage
- Diagnosis: You’ve used
suppressHydrationWarningto hide an error, but the underlying mismatch still exists and might cause other subtle bugs. The error message points to a specific attribute or element. - Fix: Remove
suppressHydrationWarningand address the root cause using the methods above. This prop is a temporary band-aid, not a solution.
After fixing these, the next error you might encounter is a client-side JavaScript error if your application relies on browser APIs that weren’t available during SSR and weren’t handled with proper checks.