|
| 1 | +--- |
| 2 | +description: React hydration mismatch prevention |
| 3 | +globs: ["**/*.tsx", "**/*.ts"] |
| 4 | +alwaysApply: false |
| 5 | +--- |
| 6 | + |
| 7 | +# Hydration Mismatch Prevention |
| 8 | + |
| 9 | +## The Problem |
| 10 | +Hydration mismatches occur when server-rendered HTML doesn't match the client-rendered output, causing UI flickers, errors, and broken interactivity. |
| 11 | + |
| 12 | +## Common AI Mistakes |
| 13 | + |
| 14 | +### 1. Non-deterministic Values in Render |
| 15 | +❌ WRONG — different value on server vs client: |
| 16 | +```tsx |
| 17 | +export default function Component() { |
| 18 | + const id = Math.random() // Different on server vs client! |
| 19 | + return <div id={`item-${id}`}>Content</div> |
| 20 | +} |
| 21 | +``` |
| 22 | + |
| 23 | +✅ CORRECT — use useId() for unique IDs: |
| 24 | +```tsx |
| 25 | +import { useId } from 'react' |
| 26 | +export default function Component() { |
| 27 | + const id = useId() |
| 28 | + return <div id={id}>Content</div> |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +### 2. Browser APIs in Server-Rendered Code |
| 33 | +❌ WRONG — window/document don't exist on server: |
| 34 | +```tsx |
| 35 | +export default function Component() { |
| 36 | + const width = window.innerWidth // ReferenceError on server! |
| 37 | + return <div>{width}</div> |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +✅ CORRECT — guard with useEffect: |
| 42 | +```tsx |
| 43 | +'use client' |
| 44 | +import { useState, useEffect } from 'react' |
| 45 | +export default function Component() { |
| 46 | + const [width, setWidth] = useState(0) |
| 47 | + useEffect(() => { setWidth(window.innerWidth) }, []) |
| 48 | + return <div>{width}</div> |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +### 3. Date/Time Rendering |
| 53 | +❌ WRONG — different timezone on server vs client: |
| 54 | +```tsx |
| 55 | +export default function Component() { |
| 56 | + return <p>{new Date().toLocaleString()}</p> // Hydration mismatch! |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +✅ CORRECT — render dates client-side or use consistent formatting: |
| 61 | +```tsx |
| 62 | +'use client' |
| 63 | +import { useState, useEffect } from 'react' |
| 64 | +export default function DateDisplay({ date }: { date: string }) { |
| 65 | + const [formatted, setFormatted] = useState(date) |
| 66 | + useEffect(() => { setFormatted(new Date(date).toLocaleString()) }, [date]) |
| 67 | + return <p>{formatted}</p> |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +## Rule |
| 72 | +- NEVER use Math.random(), crypto.randomUUID(), or Date.now() directly in render |
| 73 | +- NEVER access window, document, localStorage, or navigator without checking typeof window !== 'undefined' |
| 74 | +- Use React.useId() for dynamically generated IDs |
| 75 | +- Wrap browser-only logic in useEffect |
0 commit comments