React Hydration Error: Text Content Does Not Match
Getting hydration errors in Next.js or React? Here's why it happens and how to fix it without breaking your app.
Hydration errors occur when server-rendered HTML doesn't match what React renders on the client. This happens in Next.js and other SSR frameworks.
**Error Message:** ``` Warning: Text content did not match. Server: "December 19" Client: "December 20"
Hydration failed because the initial UI does not match what was rendered on the server ```
Root Cause
The server renders your component at one time. The client hydrates at a different time. If they produce different output, React throws a hydration error.
Common Causes
### 1. Date and Time Rendering ```javascript function Component() { return <div>{new Date().toString()}</div> // Different every render } ```
### 2. Random Values ```javascript function Component() { return <div>{Math.random()}</div> // Different every time } ```
### 3. Browser-Only APIs ```javascript function Component() { return <div>{window.innerWidth}</div> // window doesn't exist on server } ```
### 4. localStorage Access ```javascript function Component() { const theme = localStorage.getItem('theme'); return <div className={theme}>...</div> // localStorage is client-only } ```
Solution 1: useEffect for Client-Only Code
Move dynamic content to useEffect:
```javascript function Component() { const [time, setTime] = useState(''); useEffect(() => { setTime(new Date().toString()); }, []); return <div>{time}</div>; } ```
Server renders empty string. Client renders empty string, then updates. No mismatch.
Solution 2: Check Window Existence
```javascript function Component() { const width = typeof window !== 'undefined' ? window.innerWidth : 1200; return <div>Width: {width}</div>; } ```
Both server and client render the same initial value.
Solution 3: Suppress Hydration Warning
For intentional mismatches like timestamps:
```javascript <div suppressHydrationWarning> {new Date().toString()} </div> ```
Use sparingly. Only when you know the mismatch is acceptable.
Solution 4: Dynamic Import (Next.js)
Disable SSR for specific components:
```javascript import dynamic from 'next/dynamic';
const ClientComponent = dynamic( () => import('./ClientOnly'), { ssr: false } );
function Page() { return <ClientComponent />; } ```
Component only renders on client.
Practical Example
**Problem:** Showing "Last updated X minutes ago"
```javascript // This breaks function UpdateTime({ timestamp }) { return <span>{getTimeSince(timestamp)}</span>; } ```
Server calculates "2 minutes ago". User visits later, client calculates "62 minutes ago". Mismatch.
**Solution:**
```javascript function UpdateTime({ timestamp }) { const [timeAgo, setTimeAgo] = useState('');
useEffect(() => { setTimeAgo(getTimeSince(timestamp)); const interval = setInterval(() => { setTimeAgo(getTimeSince(timestamp)); }, 60000);
return () => clearInterval(interval); }, [timestamp]);
return <span>{timeAgo}</span>; } ```
Debugging Tips
1. Check browser console for the exact mismatch 2. Look for Date, Math.random, or window usage 3. Search for localStorage or sessionStorage 4. Verify all server/client code paths are identical
Prevention Checklist
- Use useEffect for time-dependent values - Check `typeof window !== 'undefined'` before using browser APIs - Load localStorage values in useEffect - Use suppressHydrationWarning only when necessary - Consider dynamic imports for client-only components
Key Takeaway
If a value changes between server and client render, move it to useEffect or use proper guards. Hydration errors indicate a real problem in SSR logic.