What is Hydration (React)?

Hydration (React): Hydration is the process where React attaches event handlers and state to HTML rendered on the server, making the static markup interactive.

With server-side rendering or static generation, the server sends complete HTML, so users see content before any JavaScript runs. That HTML is inert: buttons do nothing yet. Hydration is the step where React loads the component code in the browser, renders the same tree in memory, matches it against the existing DOM, and attaches event handlers and state without recreating the nodes. React's hydrateRoot does this, and frameworks like Next.js call it for you.

How it works

React expects the first client render to produce exactly the markup the server produced. When it does not, React reports a hydration mismatch. In development you get an error pointing at the differing node; React then falls back to rendering that part of the tree on the client, which costs time and can cause visible flicker. With Suspense boundaries, React hydrates in chunks and prioritizes the parts a user interacts with first, known as selective hydration.

Common causes of mismatches

  • Reading window, localStorage or matchMedia during render. The server has none of them, so it takes a different branch.
  • Values that differ between runs: Date.now(), Math.random(), relative timestamps and ad hoc generated IDs. Use useId for IDs.
  • Locale and time zone formatting. toLocaleString() on a server in UTC and a browser elsewhere produces different strings.
  • Invalid HTML nesting, such as a <div> inside a <p>, which the browser's parser repairs before React sees it.
  • Browser extensions and scripts that modify the DOM before hydration, including a theme script that adds a class to <html>.

Example

Render the same output on both sides first, then switch to client-only values after mount:

tsx
"use client"
import { useEffect, useState } from "react"

export function LocalTime({ iso }: { iso: string }) {
  const [text, setText] = useState(iso.slice(0, 10))
  useEffect(() => {
    setText(new Date(iso).toLocaleString())
  }, [iso])
  return <time dateTime={iso}>{text}</time>
}

For one attribute you deliberately change before hydration, such as the dark class on <html>, suppressHydrationWarning silences the warning for that element only. It works one level deep and should not be used to hide real bugs.

How MiniDev UI relates

MiniDev components take their content from props, so the same props produce the same HTML on server and client. Theme and material state live on <html> and are applied by a head script, not by component render logic, which keeps dark mode out of React's comparison and avoids FOUC. Anything that reads the clock or the viewport deserves the pattern above. React Server Components reduce hydration work further, because server-only components ship no JavaScript to hydrate.