Hydration Proof

Search documentation

Find a page or section

HP1004: Class name differs between server and client

HP1004 (class mismatch): the class attribute in the server HTML differs from React's className, often a theme or CSS-in-JS. The page keeps the server classes.

HP1004 (class-mismatch) means the class attribute in the server HTML differs from the className React renders on the client. React keeps the server classes, so the page shows the wrong theme or styles until the element re-renders. The usual causes are a dark mode check during render and CSS-in-JS class names generated in a different order.

CodeHP1004
Nameclass-mismatch
Default severityError
GroupDOM mismatches
What it meansThe class attribute rendered on the server differs from the className React renders on the client. The page keeps the server classes.

What HP1004 (class-mismatch) means

The element exists on both sides, but the set of class names differs. Classes are compared as a set, so the same names in another order do not count.

React does not patch attributes during hydration. In production, React 19 reports nothing and leaves the server classes in place: a user who prefers dark mode keeps the light theme. hydration-proof compares every reused element's classes with the className React renders, so it finds this in production builds too.

The React error it matches

Only development builds report it:

A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up.
Warning: Prop `className` did not match. Server: "theme-light" Client: "theme-dark"

The first is React 19, the second React 18. See Prop className did not match and attributes didn't match.

Likely causes

  • The theme: the server cannot know the color scheme, so a class chosen with prefers-color-scheme or a stored preference differs. The package's /dark-mode test page is found this way.
  • CSS-in-JS class names: the library generates class names from a counter, and a styled component created on one side only (or without the library's server setup) shifts it. The /css-in-js test page creates one styled component only on the server.
  • A media query or browser storage read during render.

How to fix it

  • Make class names deterministic between server and client.
  • For themes, read the preference on the server (for example from a cookie) or set the class with a pre-hydration script and suppressHydrationWarning on that element.
  • For CSS-in-JS, set up the library's server style registry so class names are generated in the same order.

A theme read from the browser during render:

theme-preview.tsx
"use client";
 
export function ThemePreview() {
  const dark =
    typeof window !== "undefined" &&
    window.matchMedia("(prefers-color-scheme: dark)").matches;
 
  return <div className={dark ? "theme-dark" : "theme-light"}>Preview</div>;
}

Store the theme in a cookie and read it on the server, so the first render already has the right class (Next.js App Router shown):

app/layout.tsx
import { cookies } from "next/headers";
import type { ReactNode } from "react";
 
export default async function RootLayout({
  children,
}: {
  children: ReactNode;
}) {
  const theme = (await cookies()).get("theme")?.value ?? "light";
 
  return (
    <html lang="en" className={theme}>
      <body>{children}</body>
    </html>
  );
}

Without a cookie (the first visit), set the class on <html> with a small inline script that runs before hydration, and mark <html> with suppressHydrationWarning. The difference is then listed as HP6001 (info), not as an error. See the theme cause for the details, and the CSS-in-JS cause for the style registry.

Prevent it with ESLint

no-match-media-in-render reports matchMedia in render code, including the color scheme query, and no-storage-in-initial-render a theme read from storage.

When the difference is intentional

A theme class set by a pre-hydration script is the case suppressHydrationWarning exists for. For a legacy area you cannot fix yet, add an ignore.issues rule with code: "HP1004", a route, a reason and an expiry date; see ignoring findings.

Example

  ✖ /settings 687ms  1 error
    HP1004 Class name differs between server and client  (theme preference (dark/light mode), 99%)
      #theme  in ThemePreview
      attribute: class
      server: "theme-light"
      client: "theme-dark"
      app/settings/ThemePreview.tsx:9:5
      → The server cannot know the color scheme. Store the theme in a cookie and read it on the server, or set the class with an inline script before hydration and mark that element with suppressHydrationWarning.