The React client-side JavaScript failed to rehydrate the server-rendered HTML because a DOM node was missing or had different content.
This happens when the HTML sent by the server doesn’t perfectly match what React expects to find on the client. It’s a hydration mismatch, and React throws this error to prevent you from ending up with a broken, inconsistent UI.
Here are the most common reasons and how to fix them:
1. Client-Side JavaScript Modifying the DOM Before Hydration
- Diagnosis: This is often the hardest to spot. If you have any client-side code that directly manipulates the DOM (e.g., using
document.getElementById().innerHTML = ...or a library that does this) before React has a chance to hydrate, it will break the match. Look for code that runs very early in your application’s lifecycle, potentially inuseEffecthooks that run on mount without proper dependency arrays, or in global scripts. - Fix: Ensure all DOM manipulation is handled by React components. If you must interact with the DOM imperatively, do it within
useEffecthooks and ensure they only run when intended, or use arefto target specific elements managed by React. For example, if you have a<div>with an ID that’s being manipulated, ensure React is the one controlling its content.// Bad: Direct DOM manipulation useEffect(() => { document.getElementById('my-element').textContent = 'Changed by client'; }, []); // Good: Let React manage content function MyComponent() { const [text, setText] = React.useState('Server Text'); return <div id="my-element">{text}</div>; } - Why it works: React expects to be the sole controller of the DOM it renders. By removing direct manipulation, you allow React to compare its virtual DOM with the server’s DOM without external interference.
2. Differences in localStorage or sessionStorage Usage
- Diagnosis: If your code attempts to access
localStorageorsessionStorageon the server (which doesn’t have these browser-specific APIs), or if the data in storage differs between server renders and client renders, you’ll get a mismatch. This often occurs inuseEffecthooks that run on mount, but also potentially in component logic that runs during server-side rendering. - Fix: Guard all access to
localStorageandsessionStoragewith checks fortypeof window !== 'undefined'.function MyComponent() { const [data, setData] = React.useState(null); React.useEffect(() => { if (typeof window !== 'undefined') { const storedData = localStorage.getItem('myKey'); setData(storedData); } }, []); return <div>{data || 'Loading...'}</div>; } - Why it works: This ensures that
localStorage/sessionStorageaccess only happens in the browser environment where these APIs are available, preventing errors during SSR and ensuring consistency.
3. Dynamic Content Based on Browser APIs (e.g., navigator)
- Diagnosis: Similar to
localStorage, APIs likenavigator.userAgent,navigator.language, orwindow.innerWidthare only available in the browser. If your component’s initial render (on the server) uses these values to determine its output, and the client’s values differ or are accessed incorrectly during SSR, you’ll see a mismatch. - Fix: Again, guard access to browser-specific APIs within
useEffector by using a conditional render that checks fortypeof window !== 'undefined'.function MyComponent() { const [isMobile, setIsMobile] = React.useState(false); React.useEffect(() => { if (typeof window !== 'undefined') { setIsMobile(window.innerWidth < 768); } }, []); return <div>{isMobile ? 'Mobile View' : 'Desktop View'}</div>; } - Why it works: By delaying the use of browser-specific APIs until the client-side, you ensure that the server renders a consistent, predictable HTML structure that the client can then hydrate correctly.
4. Inconsistent Data Fetching Between Server and Client
- Diagnosis: If your application fetches data on the server and on the client, and these fetches can return different results due to race conditions, network variations, or differing API responses, the rendered content will diverge. This is particularly common with libraries like
swrorreact-queryif not configured for SSR carefully. - Fix:
- Server-Side Data Fetching: Fetch data on the server and pass it down as props to your components. Hydrate this data on the client.
- Client-Side Only Fetching: If data is truly dynamic and not critical for initial render, fetch it only on the client within a
useEffecthook. - Framework-Specific Solutions: Use SSR data fetching utilities provided by your framework (e.g., Next.js
getServerSidePropsorgetStaticProps, Remixloaderfunctions).
// Example with Next.js (pages router) function MyPage({ initialData }) { // Use initialData from server for first render const { data } = useSWR('/api/mydata', { fallbackData: initialData }); return <div>{data.message}</div>; } export async function getServerSideProps(context) { const res = await fetch('http://localhost:3000/api/mydata'); const initialData = await res.json(); return { props: { initialData } }; } - Why it works: Ensuring that the data used for the server render is identical to the data used for the client’s initial render (or that client-side fetching is deferred) eliminates a major source of content divergence.
5. Conditional Rendering Based on Non-Deterministic Values
- Diagnosis: If your component renders different children or different content based on values that can change between server and client renders (e.g., random numbers, timestamps, user agent strings if not handled correctly), you’ll get a mismatch.
- Fix: Ensure that any conditional rendering logic relies on stable, server-generated data or is deferred until the client-side using
useEffect.function MyComponent() { const [showElement, setShowElement] = React.useState(false); React.useEffect(() => { // Simulate a client-side decision setShowElement(Math.random() > 0.5); }, []); return ( <div> {showElement && <p>This might appear!</p>} {/* Ensure a stable fallback or initial state */} {!showElement && <p>This is the stable part.</p>} </div> ); } - Why it works: By making conditional rendering deterministic on the server and then allowing client-side effects to potentially alter the DOM after initial hydration, you maintain consistency.
6. Incorrect Usage of Third-Party Libraries
- Diagnosis: Some third-party libraries, especially those that directly manipulate the DOM or rely on browser APIs, might not be SSR-friendly out-of-the-box. If they render different output on the server than they do on the client, this error will occur.
- Fix: Check the library’s documentation for SSR or universal rendering support. Often, you’ll need to:
- Wrap the component in a check for
typeof window !== 'undefined'. - Use a specific
noSsrprop if provided by the library. - Ensure the library’s output is consistent across environments.
import dynamic from 'next/dynamic'; const NonSSRComponent = dynamic( () => import('../components/MyNonSSRComponent'), { ssr: false } // This disables server-side rendering for this component ); function Page() { return ( <div> <p>Server rendered content.</p> <NonSSRComponent /> {/* This component will only render on the client */} </div> ); } - Wrap the component in a check for
- Why it works: Explicitly preventing SSR for libraries that can’t handle it, or ensuring they are used in an SSR-compatible way, guarantees that the server’s HTML is what the client expects.
The next error you’ll likely encounter if you fix this is a "Text content did not match server-rendered HTML" error, but with a slightly different, more specific node or attribute mismatch, indicating you’ve solved one problem but another subtle difference remains.