No history yet

Preventing Hydration Errors

Keep Your State in Sync

The golden rule of preventing hydration errors is simple: the first render on the client must produce the exact same HTML as what the server sent. This means the initial state used on the server to generate the page must perfectly match the initial state on the client when React takes over.

Imagine the server renders a component with a default state, but the client initializes with a different state, perhaps fetched from an API or local storage. When React tries to hydrate the server-rendered HTML, it sees a discrepancy. The client-side render doesn't match the server's version, leading to a hydration error. React will warn you and then discard the server's HTML, re-rendering everything from scratch on the client. This defeats the purpose of server-side rendering.

You must render both server and client using the same dehydrated state to ensure hydration on the client produces the exact same markup as the server.

To ensure consistency, server-side data should be serialized and sent to the client along with the HTML. This data is then used to initialize the client-side state before the first render. This process is often called 'dehydration' on the server and 'rehydration' on the client.

// A common pattern in SSR frameworks like Next.js
// Server-side code that fetches data
export async function getServerSideProps() {
  const userSettings = await fetchUserSettings(); // Fetch data
  return {
    props: {
      initialState: { settings: userSettings }
    }
  };
}

// In your page component, you receive this initial state
function SettingsPage({ initialState }) {
  // Use the state passed from the server for the initial render
  const [settings, setSettings] = useState(initialState.settings);

  // ... component logic
}

This pattern ensures that both the server and client start from the same source of truth, guaranteeing a perfect match during the initial render and a smooth, error-free hydration.

Hydrate the client using a serialized snapshot of server-side data, delivered within the initial HTML payload using a secure, minimal JSON structure embedded in a

Mind the Environment

Server-side rendering (SSR) executes your React code in a Node.js environment, not a browser. This is a critical distinction. Browser-specific APIs and global objects like window, document, and localStorage simply don't exist on the server. Attempting to access them during the server render will cause your application to crash.

Code that runs on the server can't use browser-only APIs. If you need them, wait until the component has mounted on the client.

The safest way to handle browser-specific code is to move it inside a useEffect hook with an empty dependency array. This hook only runs after the component has been rendered to the DOM on the client side, completely bypassing the server-rendering process. By then, hydration is complete, and all browser APIs are safely available.

import { useState, useEffect } from 'react';

function ThemeToggle() {
  // Initialize state to a default, server-friendly value
  const [theme, setTheme] = useState('light');

  // This code only runs on the client, after the component mounts
  useEffect(() => {
    const savedTheme = window.localStorage.getItem('theme');
    if (savedTheme) {
      setTheme(savedTheme);
    }
  }, []); // Empty array ensures this runs only once on mount

  return (
    <button>Current theme: {theme}</button>
  );
}

In this example, the component safely renders with a default 'light' theme on both the server and the client's initial render. Then, once it's running in the browser, the useEffect hook checks localStorage and updates the theme if a saved preference exists. This two-step process avoids hydration errors by ensuring the first render is identical everywhere.

Handle Dynamic Content Carefully

Content that can differ between the server and the client is a common source of hydration mismatches. Think about timestamps, random numbers, or even content that depends on the user's browser dimensions. If the server generates a timestamp of 10:30:01 and the client renders a millisecond later at 10:30:02, their outputs no longer match.

Dynamic ContentPotential Mismatch
new Date()Server and client times will differ slightly.
Math.random()Will produce different numbers on server and client.
window.innerWidthServer doesn't know the client's screen size.

The solution, much like with browser APIs, is to ensure this dynamic content is only generated on the client side after the initial hydration. Render a placeholder or default value on the server and during the initial client render. Then, use a useEffect hook to calculate and display the dynamic value.

import { useState, useEffect } from 'react';

function WelcomeMessage() {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  return (
    <div>
      <h1>Welcome!</h1>
      {isClient ? (
        <p>Your lucky number is {Math.random()}</p>
      ) : (
        <p>Calculating your lucky number...</p>
      )}
    </div>
  );
}

This component first renders a placeholder message on the server. After mounting on the client, the useEffect hook updates the isClient state, triggering a re-render that displays the randomly generated number. This approach guarantees the server and initial client renders are identical, preventing a hydration error.

Quiz Questions 1/4

What is the primary rule for preventing React hydration errors?

Quiz Questions 2/4

A developer needs to display the user's local time. Where is the safest place to calculate this time to avoid a hydration mismatch?

By keeping these principles in mind—syncing state, isolating browser code, and deferring dynamic content—you can build robust, server-rendered React applications that provide a fast, error-free user experience.