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 useEffect hooks or useState calls that directly manipulate the DOM or set initial state based on browser APIs (like window.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 useEffect or use a conditional render based on a typeof 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 useEffect runs 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: dangerouslySetInnerHTML bypasses 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 featureEnabled changes or if window becomes 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 useEffect hooks 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 key prop 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 key props 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 suppressHydrationWarning to 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 suppressHydrationWarning and 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.

Want structured learning?

Take the full React course →