How to fix Warning: Prop `key` did not match in React

ReactINTERMEDIATEMEDIUMUpdated Dec 8, 2025· 120 views

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.

Quick fix

Fixes Warning: Prop `key` did not match

Warning: Prop `key` did not match. Server: "item-1234" Client: "item-5678"
    in li (at ListComponent.tsx:15)
    in ul (at ListComponent.tsx:12)
  1. Identify the component causing the mismatch
  2. Check for non-deterministic key generation
  3. Move browser-only logic to useEffect
  4. Fix useId() in conditional loops
react — error
Warning: Prop `key` did not match. Server: "item-1234" Client: "item-5678"
in li (at ListComponent.tsx:15)
in ul (at ListComponent.tsx:12)
Warning: Prop `key` did not match

What this error means

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.

Typical symptoms

  • Console warning "Warning: Prop `key` did not match. Server: [value] Client: [different value]" during page load
  • Components in lists re-mounting unexpectedly after hydration completes
  • Visual flickers or layout shifts when the page becomes interactive
  • Component state (like input values or scroll position) being reset after hydration
  • Error only appears in production builds with SSR enabled, not in development client-only mode
  • Warning appears specifically on pages using Next.js, Gatsby, or other SSR frameworks
  • Issue occurs intermittently when using dynamic or time-based key generation

Common causes

  1. Using non-deterministic values for keys like Math.random(), Date.now(), or crypto.randomUUID() that generate different values on server and client
  2. Generating keys based on browser-only APIs (window, localStorage, sessionStorage) that are undefined during server-side rendering
  3. Using useId() hook inside conditionally rendered loops, which produces different ID sequences between server and client renders
  4. Array keys based on index position when the array order differs between server and client due to async data fetching
  5. Keys dependent on user preferences, theme, or locale that differ between server defaults and client-side values
  6. CSS-in-JS libraries generating different class name hashes on server vs client due to missing configuration
  7. Conditional rendering logic that evaluates differently on server (typeof window !== "undefined") affecting which elements receive keys
  8. Keys generated from data that changes between server render time and client hydration time

How to fix "Warning: Prop `key` did not match"

1Identify the component causing the mismatch

Open your browser console and look for the full hydration error message. React will show you the component tree and the mismatched values:

bash
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.

2Check for non-deterministic key generation

Look for any of these patterns in your key generation:

tsx
// ❌ 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).

3Move browser-only logic to useEffect

If your keys depend on browser APIs, defer that logic until after hydration:

tsx
'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.

4Fix useId() in conditional loops

If you're using React 18's useId() inside conditionally rendered loops, extract the component:

tsx
// ❌ 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.

5Ensure data consistency between server and client

Verify that the data used to generate keys is identical on server and client:

tsx
// 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.

6Suppress warning for intentional mismatches

If the key mismatch is intentional and unavoidable, suppress the warning on the specific element:

tsx
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.

7Test the fix in production mode

Hydration warnings often don't appear in development mode. Build and test your fix:

bash
# Next.js
npm run build
npm run start

# Then test the affected pages in your browser

Open 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.

Advanced notes

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

Frequently asked questions

What does "Warning: Prop `key` did not match" mean?
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.
How do I fix "Warning: Prop `key` did not match"?
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.
What causes "Warning: Prop `key` did not match"?
Using non-deterministic values for keys like Math.random(), Date.now(), or crypto.randomUUID() that generate different values on server and client Generating keys based on browser-only APIs (window, localStorage, sessionStorage) that are undefined during server-side rendering Using useId() hook inside conditionally rendered loops, which produces different ID sequences between server and client renders
Is the "Warning: Prop `key` did not match" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading