Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
openaec-foundation avatar

React Errors Hydration

  • 11 installs
  • 6 repo stars
  • Updated July 8, 2026
  • openaec-foundation/react-claude-skill-package

Helps with frontend development tasks.

About

react-errors-hydration is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted development.

  • react-errors-hydration
  • Frontend Development
  • AI-coding skill

React Errors Hydration by the numbers

  • 11 all-time installs (skills.sh)
  • Ranked #1,658 of 2,245 Frontend Development skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/react-claude-skill-package --skill react-errors-hydration

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs11
repo stars6
Last updatedJuly 8, 2026
Repositoryopenaec-foundation/react-claude-skill-package

What it does

Helps with frontend development tasks.

Files

SKILL.mdMarkdownGitHub ↗

react-errors-hydration

Quick Reference

What Hydration Is

Hydration is the process where React attaches event handlers and component state to server-rendered HTML. React expects the server-rendered DOM to EXACTLY match what the client would render on its first pass. Any difference triggers a hydration mismatch error.

Server renders HTML string ──> Browser displays HTML (fast, non-interactive)
                                       │
React hydrates ────────────────> Attaches event handlers, state, effects
                                       │
                                Interactive application

ALWAYS ensure server and client render identical output on the first render pass. Hydration does NOT patch differences -- it assumes the DOM is correct and only attaches interactivity.

NEVER treat hydration warnings as harmless. In React 18, mismatches silently produce broken UI. In React 19, React attempts recovery but at a performance cost.

---

Hydration Mismatch Diagnostic Table

Error Message / SymptomCauseFix
"Text content does not match"Different text on server vs client (Date, Math.random, locale)Use useEffect + state for dynamic values
"Expected server HTML to contain a matching <div> in <p>"Invalid HTML nesting (<div> inside <p>, <p> inside <p>)Fix HTML nesting to follow spec
"Hydration failed because the server-rendered HTML didn't match the client"Conditional rendering based on client-only stateUse useEffect for client-only branches
"There was an error while hydrating but React was able to recover" (React 19)Any mismatch -- React 19 reports and recoversFix root cause; recovery re-renders entire tree
Content flickers on page loadMismatch causes React to discard server HTML and re-renderIdentify and fix the mismatch source
Event handlers not working on server-rendered contentHydration failed silently (React 18)Check console for hydration warnings
Extra attributes like data-* from browser extensionsExtensions inject attributes after server renderIgnore if confirmed extension-caused; see Browser Extensions section
"Prop className did not match"CSS-in-JS generating different class names server vs clientConfigure SSR for your CSS-in-JS library

---

Common Causes (Ranked by Frequency)

1. Date/Time Rendering

Problem: Server and client run at different times or timezones.

// BAD: Hydration mismatch -- server time !== client time
function Header(): JSX.Element {
  return <span>{new Date().toLocaleTimeString()}</span>;
}

// GOOD: Render on client only via useEffect
function Header(): JSX.Element {
  const [time, setTime] = useState<string>("");

  useEffect(() => {
    setTime(new Date().toLocaleTimeString());
  }, []);

  return <span>{time}</span>;
}

2. Browser-Only APIs (window, localStorage, navigator)

Problem: These APIs do not exist on the server.

// BAD: window is undefined on server
function Layout(): JSX.Element {
  const width = window.innerWidth;
  return <div>{width > 768 ? <Desktop /> : <Mobile />}</div>;
}

// GOOD: Detect client with useEffect
function Layout(): JSX.Element {
  const [isClient, setIsClient] = useState<boolean>(false);

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

  if (!isClient) {
    return <div><Desktop /></div>; // Server default
  }

  return (
    <div>{window.innerWidth > 768 ? <Desktop /> : <Mobile />}</div>
  );
}

3. Conditional Rendering Based on Client State

Problem: Authentication status, feature flags, or user preferences differ between server and client.

// BAD: isLoggedIn differs on server vs client
function Nav(): JSX.Element {
  const isLoggedIn = checkAuth(); // Returns false on server, true on client
  return isLoggedIn ? <UserMenu /> : <LoginButton />;
}

// GOOD: Start with server value, update on client
function Nav(): JSX.Element {
  const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);

  useEffect(() => {
    setIsLoggedIn(checkAuth());
  }, []);

  return isLoggedIn ? <UserMenu /> : <LoginButton />;
}

4. Math.random and Non-Deterministic Values

// BAD: Different random value on server vs client
function Banner(): JSX.Element {
  const id = Math.random().toString(36).slice(2);
  return <div id={id}>Welcome</div>;
}

// GOOD: Use useId (React 18+) for stable identifiers
function Banner(): JSX.Element {
  const id = useId();
  return <div id={id}>Welcome</div>;
}

5. HTML Nesting Violations

ALWAYS follow HTML nesting rules. The browser auto-corrects invalid nesting BEFORE React hydrates, creating a DOM that does not match React's expected tree.

Invalid NestingBrowser CorrectionResult
<p><div>text</div></p>Splits into <p></p><div>text</div><p></p>Mismatch
<a><a>link</a></a>Closes outer <a> before innerMismatch
<table><div>row</div></table>Removes <div>Mismatch
<ul><div><li>item</li></div></ul>Restructures childrenMismatch

6. CSS-in-JS Class Name Mismatch

CSS-in-JS libraries (styled-components, Emotion) generate class names at runtime. If the server and client use different generation strategies or ordering, class names differ.

ALWAYS configure your CSS-in-JS library for SSR following its official documentation. For styled-components, use ServerStyleSheet. For Emotion, use extractCriticalToChunks.

---

The isClient Pattern

The standard pattern for client-only rendering:

function useIsClient(): boolean {
  const [isClient, setIsClient] = useState<boolean>(false);

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

  return isClient;
}

// Usage
function ClientOnlyFeature(): JSX.Element {
  const isClient = useIsClient();

  if (!isClient) {
    return <Placeholder />; // MUST match server output
  }

  return <RichInteractiveWidget />;
}

NEVER use typeof window !== "undefined" as a render condition -- this evaluates to true during client-side rendering of SSR apps (hydration pass), causing the mismatch you are trying to avoid.

---

suppressHydrationWarning

// Acceptable: Timestamps that intentionally differ
<time suppressHydrationWarning>
  {new Date().toISOString()}
</time>

// Acceptable: Third-party widget containers
<div suppressHydrationWarning id="third-party-widget" />

ALWAYS use suppressHydrationWarning ONLY on individual elements where the mismatch is intentional and harmless.

NEVER use suppressHydrationWarning as a blanket fix on parent containers. It only suppresses one level deep and masks real bugs.

NEVER use suppressHydrationWarning to hide mismatches you do not understand. Diagnose the root cause first.

---

hydrateRoot API

import { hydrateRoot } from "react-dom/client";
import App from "./App";

const root = hydrateRoot(
  document.getElementById("root") as HTMLElement,
  <App />,
  {
    onRecoverableError(error: unknown, errorInfo: { componentStack?: string }) {
      // Log hydration mismatches to your error tracking service
      console.error("Hydration error:", error);
      console.error("Component stack:", errorInfo.componentStack);
    },
  }
);

ALWAYS provide onRecoverableError in production to track hydration issues. React 19 uses this callback for all recovered hydration mismatches.

---

React 19 Hydration Improvements

FeatureReact 18React 19
Error messagesGeneric "did not match" textFull HTML diff showing server vs client output
RecoveryDiscards entire server-rendered tree on mismatchAttempts granular recovery, re-renders only affected subtree
ReportingConsole warning onlyonRecoverableError callback with component stack
Third-party script interferenceSilent failuresBetter tolerance for extra attributes from extensions
<style> and <link> in <head>Manual hoisting requiredNative support; React deduplicates and hoists automatically

React 19 Diff Output Example

Warning: Text content did not match.
  Server: "Hello, World"
  Client: "Hello, User"

    at Greeting (app/components/Greeting.tsx:5:3)
    at Layout (app/layout.tsx:12:5)

React 19 shows the EXACT server value vs client value plus the full component stack. Use this to trace the mismatch source directly.

---

Browser Extension Interference

Browser extensions (ad blockers, password managers, translation tools, accessibility plugins) inject or modify DOM elements AFTER server render but BEFORE React hydration.

Symptoms

  • Hydration errors in production that are NOT reproducible locally
  • Extra <div>, <style>, or data-* attributes in the DOM
  • Errors disappear in incognito mode

Diagnosis

1. Open the page in incognito mode (extensions disabled) 2. If the error disappears, an extension is the cause 3. Use onRecoverableError to log and filter these in production

Mitigation

  • NEVER restructure your app to work around extension interference
  • ALWAYS use onRecoverableError to detect and filter extension-caused errors
  • Consider wrapping known injection targets with suppressHydrationWarning ONLY if confirmed extension-caused

---

Decision Tree: Fixing Hydration Errors

Hydration error detected
├── Is the content time-dependent (Date, timestamp)?
│   └── YES → Use useEffect + state OR suppressHydrationWarning
├── Does it use browser APIs (window, localStorage, navigator)?
│   └── YES → Use the isClient pattern or dynamic import with ssr: false
├── Is it conditional on user/auth state?
│   └── YES → Default to logged-out on server, update in useEffect
├── Is it an HTML nesting violation?
│   └── YES → Fix the HTML structure (no <div> in <p>, etc.)
├── Is it CSS-in-JS class names?
│   └── YES → Configure SSR for your CSS-in-JS library
├── Does it only happen in production with real users?
│   └── YES → Likely browser extension interference; use onRecoverableError
└── None of the above?
    └── Check for: different environment variables, different API responses,
        third-party scripts loading before hydration

---

Dynamic Import for Client-Only Components

When a component fundamentally cannot render on the server:

// Next.js
import dynamic from "next/dynamic";

const MapWidget = dynamic(() => import("./MapWidget"), {
  ssr: false,
  loading: () => <div>Loading map...</div>,
});

// Generic React with React.lazy (client-only, not for SSR)
const MapWidget = React.lazy(() => import("./MapWidget"));

ALWAYS provide a loading fallback that matches the server-rendered placeholder to prevent layout shift.

---

Reference Links

  • references/examples.md -- Hydration error examples with complete fix patterns
  • references/anti-patterns.md -- Common hydration mistakes and why they fail

Official Sources

  • https://react.dev/reference/react-dom/client/hydrateRoot
  • https://react.dev/link/hydration-mismatch
  • https://react.dev/reference/react/useId
  • https://react.dev/reference/react-dom/client/hydrateRoot#handling-different-client-and-server-content

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.