No history yet

Common Causes of Hydration Errors

When Hydration Fails

React’s hydration process relies on a simple promise: the HTML rendered on the server must be identical to the HTML rendered during the initial render on the client. When this promise is broken, React gets confused and throws a hydration error. It’s like following a map to a meeting point, only to find the street layout has changed since the map was printed. Let's look at the most common reasons this happens.

Mismatched HTML Structure

Sometimes, the structure of the HTML sent by the server is not what the browser ends up building in the DOM. This usually happens when the server-rendered HTML is invalid. Browsers have built-in error correction and will often try to "fix" malformed markup before React even gets a chance to see it.

A classic example is nesting a block-level element like a <div> inside a paragraph element like <p>. This is not valid HTML.

// Server-side React renders this component
function InvalidComponent() {
  return (
    <p>
      This is a paragraph.
      <div>This div is incorrectly nested.</div>
    </p>
  );
}

The server will happily send this markup to the browser. However, the browser knows a <div> doesn't belong inside a <p>. To fix this, it will alter the DOM, likely creating a structure that looks more like this:

<!-- The browser's corrected DOM -->
<p>This is a paragraph.</p>
<div>This div is incorrectly nested.</div>

When the client-side React code runs, it expects to find the <div> inside the <p>. But it finds two separate elements instead. This discrepancy between the expected virtual DOM and the actual browser DOM causes hydration to fail. The same issue can occur with incorrect nesting in tables, such as a <div> inside a <tbody> where only <tr> elements are allowed.

Dynamic or Unpredictable Content

Another common source of errors is content that is different on the server and the client. If your component generates a value that is guaranteed to change between the server render and the client render, you'll get a mismatch.

Think about things like timestamps, random numbers, or unique IDs generated on the fly. The server generates one value, and by the time the client code executes moments later, it generates a completely different one.

function UnpredictableComponent() {
  // This will generate a different number on the server and client
  const randomNumber = Math.random();

  return <div>Your lucky number is: {randomNumber}</div>;
}

The server might render <div>Your lucky number is: 0.815</div>. But when the component renders on the client, Math.random() runs again and might produce 0.423. React sees the difference in the text content and reports a hydration error because the server's output doesn't match.

To fix this, ensure that unpredictable values are only generated on the client side, typically inside a useEffect hook, which runs after the initial hydration is complete.

Using Browser-Only APIs

Finally, hydration errors can occur when your server-side code tries to use APIs that only exist in the browser. The server is a Node.js environment; it doesn't have a window, document, or localStorage object. Calling these directly in your component's rendering logic will cause a crash on the server or lead to mismatches.

Lesson image

Imagine a component that tries to render the browser's current URL or window dimensions:

function BrowserSpecificComponent() {
  // This will fail on the server, as `window` is not defined
  const screenWidth = window.innerWidth;

  return <div>Screen width is: {screenWidth}px</div>;
}

This code will throw an error during the server render because window does not exist. A more subtle issue arises if you conditionally render something based on a browser API. For example, checking if window.innerWidth > 600 might result in one component tree on the client (where window exists) and a different one on the server (where the check might be bypassed or handled differently), causing a mismatch.

Just like with dynamic content, the solution is to move any logic that relies on browser APIs into a useEffect hook. This ensures the code only runs on the client, after the initial server-rendered HTML has been successfully hydrated.

Safely accessing browser elements enables you to avoid reconciliation errors when ReactDOM.hydrates a site from HTML to React.

Understanding these common pitfalls is the first step toward writing robust, error-free server-rendered React applications. By ensuring your markup is valid, your content is consistent, and your code is environment-aware, you can provide a smooth and fast user experience.

Quiz Questions 1/5

What is the fundamental condition that must be met to avoid a React hydration error?

Quiz Questions 2/5

Why would nesting a <div> directly inside a <tbody> element in your server-rendered JSX likely cause a hydration error?