How to fix Warning: Prop `className` did not match. Server: "..." Client: "..." in React
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"- Identify which component is causing the mismatch
- For styled-components: Install and configure babel plugin
- For Material-UI: Use single cache instance
- Fix conditional rendering based on client-side state
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
- 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
- Material-UI components using different class name generators on server vs client
- Theme values (dark mode, user preferences) initialized differently on server and client
- Version mismatch between Material-UI or styled-components on server and client bundles
- Client-side state accessed during initial render before hydration completes
- Random values, timestamps, or IDs used in className generation
- CSS modules with different hash generation between build environments
How to fix "Warning: Prop `className` did not match. Server: "..." Client: "...""
Check the console warning carefully - it typically shows both the server and client className values:
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.
The babel plugin ensures deterministic class name generation. Install it:
npm install --save-dev babel-plugin-styled-componentsAdd to your .babelrc or babel.config.js:
{
"plugins": [
[
"babel-plugin-styled-components",
{
"ssr": true,
"displayName": true,
"preprocess": false
}
]
]
}For Next.js, add to next.config.js:
module.exports = {
compiler: {
styledComponents: true,
},
}Restart your development server after configuration changes.
Create a shared Emotion cache for both server and client. Create lib/createEmotionCache.ts:
import createCache from '@emotion/cache';
export default function createEmotionCache() {
return createCache({ key: 'css', prepend: true });
}In your pages/_app.tsx (Next.js):
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:
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 };
}
}Never use browser APIs during initial render. Use state with useEffect instead:
❌ Wrong - causes hydration mismatch:
function ThemeToggle() {
const isDark = localStorage.getItem('theme') === 'dark';
return <div className={isDark ? 'dark' : 'light'}>...</div>;
}✅ Correct - consistent server/client render:
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.
Version mismatches can cause different hash generation. Check your package.json and lock file:
npm list styled-components
npm list @emotion/react
npm list @mui/materialDelete node_modules and lock file, then reinstall:
rm -rf node_modules package-lock.json
npm installIf using monorepos or micro-frontends, ensure all packages use the exact same versions:
{
"dependencies": {
"styled-components": "6.1.8",
"@emotion/react": "11.11.3"
}
}As a last resort for components that must differ (timestamps, random IDs), suppress the specific warning:
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.