Fix "Prop className did not match" Error in Next.js & Tailwind
Resolve the 'Prop className did not match' warning in Next.js. Understand how CSS-in-JS and conditional classes cause server/client mismatches and how to fix them.
FlowQL Team
AI Search Optimization Experts
Introduction
It's a variation of the classic hydration error, but this one is specific to styling. You see: "Warning: Prop className did not match. Server: 'bg-red-500' Client: 'bg-blue-500'"
This error is rampant in the "Vibe Stack" (Next.js + Tailwind + Shadcn UI). It means your server thinks an element should look one way, but your browser thinks it should look another. This often causes flickering Dark Mode toggles or broken responsive layouts.
Why Do ClassNames Mismatch?
What causes className mismatches in Next.js?
ClassName mismatches occur when the logic determining your CSS classes yields different results on the server (Node.js) versus the client (Browser). Common culprits include checking window.innerWidth for responsive styles, reading localStorage for themes (Dark/Light mode) before hydration, or using random class generators that aren't seeded consistently.
If your code says className={isMobile ? 'p-4' : 'p-8'}, the server (which has no screen width) might guess "false", while the mobile browser says "true". Boom—mismatch.
How the Mismatch Happens:
graph TD
A[Code: isMobile ? 'small' : 'large'] -->|Server Render| B(isMobile = false)
A -->|Client Render| C(isMobile = true)
B --> D[Class: 'large']
C --> E[Class: 'small']
D --> F{Mismatch?}
E --> F
F -->|Yes| G[Console Error]
style G fill:#ff9999,stroke:#333,stroke-width:2px
The Flow:
- Server renders
isMobile = false→ Class:'large' - Client renders
isMobile = true→ Class:'small' - Result: Mismatch Error!
Dynamic styling based on browser APIs is the #1 cause of this error.
The Fixes
1. The "Mounted" Pattern (Best for Dark Mode/Mobile)
If you need to render something different based on the browser state, you must wait until the component is "mounted."
"use client"
import { useState, useEffect } from 'react'
export default function ThemeToggle() {
const [mounted, setMounted] = useState(false)
// Only run on client
useEffect(() => {
setMounted(true)
}, [])
// Render nothing (or a skeleton) on server to match initial client state
if (!mounted) {
return null
}
return (
<div className={window.localStorage.getItem('theme') === 'dark' ? 'bg-black' : 'bg-white'}>
Toggle
</div>
)
}
2. Avoid typeof window in Rendering Logic
Never use typeof window !== 'undefined' directly in your JSX className.
Bad:
<div className={typeof window !== 'undefined' && window.innerWidth < 768 ? 'mobile' : 'desktop'} />
Good: use CSS media queries (Tailwind's md:, lg:) instead of JS conditionals.
<div className="mobile md:desktop" />
CSS is handled by the browser engine after hydration, so it never causes a React mismatch.
3. Third-Party Libraries
Libraries like framer-motion or ad scripts often inject classes. Ensure you are using the latest versions compatible with Next.js 14/15.
FlowQL: Styling Without the Struggle
At FlowQL, we see this constantly in projects using extensive conditional UI logic.
It's tempting to use JavaScript for everything, but moving logic back to CSS (via Tailwind modifiers) is often the robust solution. When you're fighting the framework instead of building features, we step in to simplify your architecture.
Conclusion
Your Action Plan:
- Audit Conditionals: Look for
className={condition ? ...}. - Move to CSS: Can
window.innerWidthlogic be replaced bymd:hidden? - Use Mounted State: If you must access the browser, wait for
useEffect.
Stop the flicker. [Book a session with FlowQL] to stabilize your UI.
FAQ
Why does my Dark Mode flicker on refresh?
Your Dark Mode flickers because the server renders the default "Light" HTML first. The browser downloads it, paints it white, then hydrates, reads localStorage, and suddenly paints it black. This "Flash of Unstyled Content" (FOUC) is a classic hydration issue.
Can I use suppressHydrationWarning on classNames?
Yes, you can use suppressHydrationWarning on specific elements if the class mismatch is unavoidable (like a timestamp color). However, it's better to fix the underlying logic to ensure consistency.
Is this unique to Tailwind?
No, this error happens with any styling method (Styled Components, CSS Modules, vanilla CSS) if the class names are generated dynamically based on browser-only state during the initial render.
Subscribe to our blog
Get the latest guides and insights delivered to your inbox.
Join the FlowQL waitlist
Get early access to our AI search optimization platform.
Related Articles
Fix: params are undefined in Next.js App Router Server Component
params undefined in your Next.js server component? Learn how to handle the async breaking change in Next.js 15 and properly await your dynamic route data.
Fix: 'NextRouter was not mounted' in Next.js App Directory
Getting the 'NextRouter was not mounted' error? Learn why next/router fails in the App Router and how to correctly use next/navigation for Next.js 14 and 15.
Fix: Shadcn Select Dropdown Hidden or Behind Other Elements
Shadcn Select dropdown not showing up? Learn how to fix z-index conflicts and properly use Portals to break through CSS stacking contexts in Tailwind.