Guides & Tutorials2025-01-02·4 min read

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.

#nextjs#tailwind#css#debugging

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:

  1. Audit Conditionals: Look for className={condition ? ...}.
  2. Move to CSS: Can window.innerWidth logic be replaced by md:hidden?
  3. 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