How to fix Warning: Prop `className` did not match. Server: "..." Client: "..." in React

ReactINTERMEDIATEMEDIUMUpdated Dec 9, 2025· 113 views

This warning appears during React hydration when the className generated on the server differs from the one generated on the client. Commonly caused by CSS-in-JS libraries like styled-components, Material-UI, or conditional rendering that differs between server and client.

Quick fix

Fixes Warning: Prop `className` did not match. Server: "..." Client: "..."

Warning: Prop `className` did not match.
Server: "css-abc123 MuiButton-root"
Client: "css-xyz789 MuiButton-root"
  1. Identify which component is causing the mismatch
  2. For styled-components: Install and configure babel plugin
  3. For Material-UI: Use single cache instance
  4. Fix conditional rendering based on client-side state
react — error
Warning: Prop `className` did not match.
Server: "css-abc123 MuiButton-root"
Client: "css-xyz789 MuiButton-root"
Warning: Prop `className` did not match. Server: "..." Client: "..."

What this error means

This hydration warning occurs when React compares the server-rendered HTML with the client-side React tree and discovers that the `className` prop on a component or element doesn't match between the two environments. During server-side rendering (SSR), React generates HTML on the server and sends it to the browser. When the JavaScript bundle loads on the client, React performs "hydration" - it attaches event listeners and initializes the React component tree by comparing it to the existing server-rendered HTML. If the className values differ, React detects this mismatch and logs a warning. This is particularly common with CSS-in-JS libraries (styled-components, Emotion, JSS) that generate dynamic class names. If the server generates `css-abc123` but the client generates `css-xyz789`, React flags this inconsistency. The mismatch can cause visual flashing, incorrect styles, or accessibility issues.

Typical symptoms

  • Console warning showing different className values for server vs client
  • Brief flash of unstyled content (FOUC) when page loads
  • Components appear with incorrect styles initially, then suddenly update
  • Warning appears in development mode but may be silent in production
  • Issue occurs specifically with server-side rendered pages (Next.js, Remix, etc.)
  • Multiple similar warnings for different components using the same styling library

Common causes

  1. CSS-in-JS libraries (styled-components, Emotion, JSS) generating non-deterministic class names between server and client
  2. Missing or incorrectly configured babel plugin for styled-components
  3. Conditional rendering based on browser-only APIs (window, localStorage) executed during initial render
  4. Material-UI components using different class name generators on server vs client
  5. Theme values (dark mode, user preferences) initialized differently on server and client
  6. Version mismatch between Material-UI or styled-components on server and client bundles
  7. Client-side state accessed during initial render before hydration completes
  8. Random values, timestamps, or IDs used in className generation
  9. CSS modules with different hash generation between build environments

How to fix "Warning: Prop `className` did not match. Server: "..." Client: "...""

1Identify which component is causing the mismatch

Check the console warning carefully - it typically shows both the server and client className values:

bash
Warning: Prop `className` did not match.
Server: "css-abc123 MuiButton-root"
Client: "css-xyz789 MuiButton-root"

Use React DevTools to inspect the component tree and identify which specific component is triggering the warning. Look for components that use CSS-in-JS libraries or conditional styling.

2For styled-components: Install and configure babel plugin

The babel plugin ensures deterministic class name generation. Install it:

bash
npm install --save-dev babel-plugin-styled-components

Add to your .babelrc or babel.config.js:

json
{
  "plugins": [
    [
      "babel-plugin-styled-components",
      {
        "ssr": true,
        "displayName": true,
        "preprocess": false
      }
    ]
  ]
}

For Next.js, add to next.config.js:

javascript
module.exports = {
  compiler: {
    styledComponents: true,
  },
}

Restart your development server after configuration changes.

3For Material-UI: Use single cache instance

Create a shared Emotion cache for both server and client. Create lib/createEmotionCache.ts:

typescript
import createCache from '@emotion/cache';

export default function createEmotionCache() {
  return createCache({ key: 'css', prepend: true });
}

In your pages/_app.tsx (Next.js):

typescript
import { CacheProvider } from '@emotion/react';
import createEmotionCache from '../lib/createEmotionCache';

const clientSideEmotionCache = createEmotionCache();

function MyApp({ Component, emotionCache = clientSideEmotionCache, pageProps }) {
  return (
    <CacheProvider value={emotionCache}>
      <Component {...pageProps} />
    </CacheProvider>
  );
}

For server-side, in pages/_document.tsx:

typescript
import createEmotionServer from '@emotion/server/create-instance';
import createEmotionCache from '../lib/createEmotionCache';

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const cache = createEmotionCache();
    const { extractCriticalToChunks } = createEmotionServer(cache);

    const originalRenderPage = ctx.renderPage;
    ctx.renderPage = () =>
      originalRenderPage({
        enhanceApp: (App) => (props) => <App emotionCache={cache} {...props} />,
      });

    const initialProps = await Document.getInitialProps(ctx);
    const emotionStyles = extractCriticalToChunks(initialProps.html);

    return { ...initialProps, emotionStyles };
  }
}
4Fix conditional rendering based on client-side state

Never use browser APIs during initial render. Use state with useEffect instead:

❌ Wrong - causes hydration mismatch:

typescript
function ThemeToggle() {
  const isDark = localStorage.getItem('theme') === 'dark';
  return <div className={isDark ? 'dark' : 'light'}>...</div>;
}

✅ Correct - consistent server/client render:

typescript
function ThemeToggle() {
  const [isDark, setIsDark] = useState(false);

  useEffect(() => {
    setIsDark(localStorage.getItem('theme') === 'dark');
  }, []);

  return <div className={isDark ? 'dark' : 'light'}>...</div>;
}

The component renders with the default state on both server and client, then updates after hydration in the browser.

5Ensure CSS-in-JS library versions match

Version mismatches can cause different hash generation. Check your package.json and lock file:

bash
npm list styled-components
npm list @emotion/react
npm list @mui/material

Delete node_modules and lock file, then reinstall:

bash
rm -rf node_modules package-lock.json
npm install

If using monorepos or micro-frontends, ensure all packages use the exact same versions:

json
{
  "dependencies": {
    "styled-components": "6.1.8",
    "@emotion/react": "11.11.3"
  }
}
6Use suppressHydrationWarning for unavoidable mismatches

As a last resort for components that must differ (timestamps, random IDs), suppress the specific warning:

typescript
function Timestamp() {
  const [time, setTime] = useState(new Date().toISOString());

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

  return (
    <div suppressHydrationWarning>
      {time}
    </div>
  );
}

Warning: Only use this when you understand why the mismatch occurs and cannot fix it. Overuse can hide real bugs.

Advanced notes

Understanding CSS-in-JS Hash Generation

CSS-in-JS libraries generate class names by hashing the styles and component metadata. The hash should be deterministic (same inputs = same output), but several factors can break this:

- Component identifiers: Styled-components uses component display names in hashes. Without the babel plugin, minified production builds generate different names than development.
- Style insertion order: Emotion and JSS maintain a style sheet insertion order. If server and client insert styles in different orders, class names may differ.
- Dynamic values: Avoid using Math.random(), Date.now(), or crypto.randomUUID() in style definitions.

Next.js App Router Considerations

With Next.js 13+ App Router and React Server Components, be aware of the server/client boundary:

- Server Components render once on the server and don't hydrate
- Client Components (marked with "use client") undergo full hydration
- Only Client Components can use browser APIs or interactive hooks
- Move CSS-in-JS styled components to Client Components when possible

Production vs Development Differences

The warning may appear only in development or only in production:

- Development builds include more debugging information (component names, stack traces)
- Production builds are minified, changing component identifiers
- NODE_ENV differences can trigger conditional code paths
- Always test SSR pages in production mode before deploying: npm run build && npm run start

Material-UI v4 vs v5+

Material-UI v5 switched from JSS to Emotion, requiring different solutions:

- v4: Use ServerStyleSheets and remove jss-server-side element
- v5+: Use Emotion cache as shown in the solutions above
- Migration guides: https://mui.com/material-ui/migration/migration-v4/

Debugging with React DevTools Profiler

Enable "Highlight updates" in React DevTools to visualize which components re-render during hydration. className mismatches often cause unnecessary re-renders, visible as flashing highlights.

Frequently asked questions

What does "Warning: Prop `className` did not match. Server: "..." Client: "..."" mean?
This hydration warning occurs when React compares the server-rendered HTML with the client-side React tree and discovers that the className prop on a component or element doesn't match between the two environments. During server-side rendering (SSR), React generates HTML on the server and sends it to the browser. When the JavaScript bundle loads on the client, React performs "hydration" - it attaches event listeners and initializes the React component tree by comparing it to the existing server-rendered HTML.
How do I fix "Warning: Prop `className` did not match. Server: "..." Client: "...""?
This warning appears during React hydration when the className generated on the server differs from the one generated on the client. Commonly caused by CSS-in-JS libraries like styled-components, Material-UI, or conditional rendering that differs between server and client.
What causes "Warning: Prop `className` did not match. Server: "..." Client: "...""?
CSS-in-JS libraries (styled-components, Emotion, JSS) generating non-deterministic class names between server and client Missing or incorrectly configured babel plugin for styled-components Conditional rendering based on browser-only APIs (window, localStorage) executed during initial render
Is the "Warning: Prop `className` did not match. Server: "..." Client: "..."" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading