keepcalculator

06 Numbers and Powers

Fix Theme Hydration Without Mounted Flag

Card No. 06·4749 · Updated · Standard formula, shown below

Theme hydration errors happen when the server-rendered HTML doesn’t match what React renders on the client. You can fix them without hiding your app behind a mounted flag by ensuring your components render identical output on the server and client, and by using built-in React features like suppressHydrationWarning or useSyncExternalStore for legitimate client-only differences. This article explains the causes and provides step-by-step fixes for the most common scenarios.

What Causes Hydration Mismatches

Hydration is React’s process of attaching event listeners and state to the server-rendered HTML. A mismatch occurs when the client’s first render produces different HTML than what the server sent. Common culprits include:

  • Date and time – displaying new Date() or locale-specific values that differ between server and client.
  • Browser-only APIs – using window, document, or localStorage during render.
  • Random values – generating IDs or random numbers during render.
  • Third-party scripts – scripts that modify the DOM before hydration.

The mounted flag (often useState(false) + useEffect(() => setMounted(true), [])) is a common workaround, but it forces a second render and delays content, hurting performance and SEO. Better solutions address the root cause.

Fix 1: Use suppressHydrationWarning for Text Differences

If the mismatch is only in text content (not attributes or structure), you can add suppressHydrationWarning to the specific element. This tells React to ignore mismatched text on that element and its children.

function TimeDisplay() {
  return (
    <time suppressHydrationWarning>
      {new Date().toLocaleTimeString()}
    </time>
  );
}

Use this only for elements that are expected to differ, like timestamps or user-specific greetings. It does not suppress attribute mismatches, and overusing it can hide real bugs.

Fix 2: Defer Browser-Only Code to useEffect

For values that depend on window or document, move the computation into a useEffect hook and update state afterward. This ensures the server and client first render match.

function WindowWidth() {
  const [width, setWidth] = React.useState(0);
  React.useEffect(() => {
    setWidth(window.innerWidth);
  }, []);
  return <p>Width: {width}</p>;
}

This still causes a second render, but only for the specific component, not the whole page. Use this for values that are genuinely only known on the client, like viewport size or localStorage data.

Fix 3: Use useSyncExternalStore for External Stores

If you’re reading from an external store (like Redux or a custom event emitter) that may have different values on server and client, use useSyncExternalStore. It ensures the client render matches the server snapshot, then updates after hydration.

const width = useSyncExternalStore(
  store.subscribe,
  () => store.getWidth(),
  () => 0 // server snapshot
);

This is the recommended approach for values that change over time and are stored outside React state. It avoids the mounted flag entirely and is more efficient.

Fix 4: Normalize Random Values

If you generate IDs or random numbers during render, use a stable seed or generate them in an effect. For example, use useId() for React-generated IDs, which is hydration-safe.

const id = React.useId();

For other random values, generate them in a useEffect and store in state, or use a deterministic algorithm based on server data.

Fix 5: Check for Third-Party Scripts

Some analytics or ad scripts modify the DOM before React hydrates, causing mismatches. If you cannot remove the script, you can:

  • Load the script after hydration using useEffect.
  • Use suppressHydrationWarning on the affected elements.
  • Ensure the script does not alter the HTML structure.

When to Use a Mounted Flag (and When Not)

A mounted flag is acceptable for small, isolated components that must render differently on the client (e.g., a dark mode toggle that reads localStorage). However, it is a bad default for the whole app because it causes a flash of unstyled content and defeats SSR benefits. Use it only when no other solution works, and keep it scoped to the specific component.

Frequently Asked Questions

Why does my hydration error only appear in production?

Production builds often minify and reorder code, and some libraries behave differently in strict mode. The error may also be masked in development by React’s warnings. Check the console in a production build to see the exact mismatch.

Can I ignore hydration errors?

No. They can cause UI glitches, broken event handlers, and performance issues. React may also re-render the entire tree, negating SSR benefits. Fix them properly.

What is the difference between hydration and SSR?

SSR (Server-Side Rendering) generates HTML on the server. Hydration is the client-side process of making that HTML interactive. Hydration errors occur when the two don’t match.

Does suppressHydrationWarning fix all errors?

No. It only suppresses text mismatches on the specified element. Attribute or structural mismatches still cause errors. Use it sparingly and only for known differences.

Is useSyncExternalStore better than useEffect for hydration?

For external data that changes over time, yes. It avoids the extra render and is more performant. For one-time values like window width, useEffect is simpler.

Will these fixes work with Next.js or Remix?

Yes, these are React features and work in any React-based framework. The same principles apply, though some frameworks have additional built-in solutions (like Next.js’s dynamic import with ssr: false).

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →