This hydration warning occurs when the key prop on a React element differs between server-side rendering and client-side hydration. It typically happens with dynamic content, unstable key generation, or browser-only logic that runs during initial render.
This warning is a React hydration error that occurs during server-side rendering (SSR) when the `key` prop assigned to an element on the server doesn't match the `key` prop React expects during client-side hydration. Hydration is the process where React converts pre-rendered HTML from the server into a fully interactive application by attaching event handlers and reconciling the virtual DOM. React expects the server-rendered HTML and the initial client render to be identical, including all props like `key`. When React detects that a `key` prop differs between server and client, it issues this warning because the mismatch can cause incorrect DOM updates, lost component state, or rendering inconsistencies. The `key` prop is critical for React's reconciliation algorithm—it helps React identify which elements changed, were added, or were removed in lists.
Open your browser console and look for the full hydration error message. React will show you the component tree and the mismatched values:
Warning: Prop `key` did not match. Server: "item-1234" Client: "item-5678"
in li (at ListComponent.tsx:15)
in ul (at ListComponent.tsx:12)The stack trace tells you which component is rendering elements with mismatched keys. Navigate to that file and examine how keys are being generated.
Look for any of these patterns in your key generation:
// ❌ BAD: Random keys differ between server and client
{items.map(item => (
<div key={Math.random()}>{item.name}</div>
))}
// ❌ BAD: Timestamp keys differ
{items.map(item => (
<div key={`${item.id}-${Date.now()}`}>{item.name}</div>
))}
// ✅ GOOD: Use stable, deterministic keys
{items.map(item => (
<div key={item.id}>{item.name}</div>
))}Replace any non-deterministic key generation with stable identifiers from your data (IDs, slugs, or unique combinations of properties).
If your keys depend on browser APIs, defer that logic until after hydration:
'use client';
import { useState, useEffect } from 'react';
function UserPreferencesList({ items }) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
// During SSR and initial hydration, use server-safe keys
if (!mounted) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
// After hydration, safe to use localStorage
const savedOrder = localStorage.getItem('itemOrder');
const sortedItems = sortByPreference(items, savedOrder);
return (
<ul>
{sortedItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}This ensures keys remain consistent during the critical hydration phase.
If you're using React 18's useId() inside conditionally rendered loops, extract the component:
// ❌ BAD: useId() in conditional loop causes ID sequence mismatch
function BadList({ items, showDetails }) {
return (
<ul>
{items.map(item => {
const id = useId(); // Different IDs on server vs client!
return showDetails ? (
<li key={id}>{item.name}</li>
) : null;
})}
</ul>
);
}
// ✅ GOOD: Extract component so useId() is called consistently
function ListItem({ item }) {
const id = useId();
return <li key={id}>{item.name}</li>;
}
function GoodList({ items, showDetails }) {
return (
<ul>
{items.filter(() => showDetails).map(item => (
<ListItem key={item.id} item={item} />
))}
</ul>
);
}Alternatively, just use stable data-based keys instead of useId() for list items.
Verify that the data used to generate keys is identical on server and client:
// In Next.js, use consistent data fetching
export async function generateStaticParams() {
const items = await fetchItems();
return items.map(item => ({ id: item.id }));
}
export default async function Page({ params }) {
// Fetch the same data on the server
const items = await fetchItems();
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}Avoid mixing server data with client-side filtering or sorting that could reorder items before hydration completes.
If the key mismatch is intentional and unavoidable, suppress the warning on the specific element:
function SessionBasedList({ items }) {
const sessionKey = typeof window !== 'undefined'
? sessionStorage.getItem('sessionId')
: 'server-session';
return (
<ul suppressHydrationWarning>
{items.map(item => (
<li key={`${sessionKey}-${item.id}`} suppressHydrationWarning>
{item.name}
</li>
))}
</ul>
);
}Warning: Only use suppressHydrationWarning as a last resort. It only works one level deep and doesn't fix the underlying issue—React may still have hydration problems that cause bugs. Always prefer fixing the root cause.
Hydration warnings often don't appear in development mode. Build and test your fix:
# Next.js
npm run build
npm run start
# Then test the affected pages in your browserOpen the browser console and verify the "Prop key did not match" warning no longer appears. Check that list items render correctly and maintain their state after hydration.
Framework-Specific Considerations:
- Next.js App Router: Use dynamic(() => import(...), { ssr: false }) to completely disable SSR for components that need browser-only keys, though this defeats the purpose of SSR for that component.
- Next.js Pages Router: Wrap problematic components with typeof window !== 'undefined' checks, but be aware this creates a hydration boundary that may cause visual flickers.
- Remix: Leverage the useHydrated hook from remix-utils to conditionally render after hydration completes.
CSS-in-JS Libraries:
Libraries like styled-components or Emotion can cause key mismatches if not properly configured for SSR. Ensure you:
- Include the Babel plugin for your CSS-in-JS library
- Set up the server-side style extraction properly
- Use deterministic class name generation (disable development-mode random suffixes)
Performance Implications:
Key mismatches force React to tear down and rebuild DOM nodes instead of reusing them, which:
- Increases Time to Interactive (TTI) as React does extra work during hydration
- Can cause layout shifts that hurt Core Web Vitals (CLS score)
- May trigger unnecessary effect cleanup and re-runs in child components
Debugging Complex Cases:
If you can't locate the source:
1. Use React DevTools Profiler to see which components are re-rendering during hydration
2. Add console.logs in suspected components with typeof window checks to verify they run on both server and client
3. Temporarily add suppressHydrationWarning to narrow down which element is causing the issue, then remove it once you fix the root cause
Prevention:
- Always use stable, deterministic keys derived from your data (IDs, slugs, combinations of unique properties)
- Avoid mixing client-side state or browser APIs into key generation
- Test SSR builds regularly—don't rely solely on client-side development mode
- Use ESLint plugins like eslint-plugin-react to catch common key anti-patterns
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite