Hydration Proof

Search documentation

Find a page or section

HP1002: Attribute differs between server and client

HP1002 (attribute mismatch): an attribute has one value in the server HTML and another in React's client render, and React never fixes it. Causes and fixes.

HP1002 (attribute-mismatch) means an attribute such as href, id or data-* has one value in the server HTML and another in the props React renders on the client. React does not patch attributes during hydration, so users keep the server value. Compute the attribute from data both sides share.

CodeHP1002
Nameattribute-mismatch
Default severityError
GroupDOM mismatches
What it meansAn attribute value in the server HTML differs from the value React rendered on the client. React does not patch attributes during hydration.

What HP1002 (attribute-mismatch) means

The element exists on both sides and React reuses it, but one attribute differs. React keeps whatever the server sent until the element re-renders for another reason: a link keeps pointing at the server's URL, an aria-* value stays wrong, a datetime stays stale.

Attribute codes are split by what differs:

DifferenceCode
A value on both sides, but differentHP1002
styleHP1003
classHP1004
Only in the server HTMLHP1005
Only in React's client renderHP1006

hydration-proof compares every attribute React reused with the props React renders for that element, so it finds attribute mismatches in production builds, where React 19 reports nothing. Boolean attributes such as disabled and hidden are compared by presence. An attribute that changes during the hydration commit, for example from a layout effect, is an update and not a mismatch, so it is not reported.

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 `href` did not match. Server: "/pricing?region=eu-west" Client: "/pricing?region=us-east"

The first is React 19, the second React 18. See attributes didn't match and Prop className did not match for the React 18 warning, which names the prop that differs.

Likely causes

  • A typeof window branch or another browser-only API in the value, like the package's /attr-mismatch test page, which builds an href from a region that differs on each side.
  • The theme: an SVG fill chosen with prefers-color-scheme, as on the /svg-attr test page.
  • A generated id from a counter or a random value in id, for or aria-* attributes.
  • A time-dependent value in datetime or title, or a random value anywhere.

How to fix it

  • Compute the attribute from data that is identical on the server and the client.
  • React does not fix attributes during hydration, so users keep seeing the server value until the next update.

Here the server and the browser pick a different region:

pricing-link.tsx
"use client";
 
export function PricingLink() {
  const region = typeof window === "undefined" ? "eu-west" : "us-east";
  return <a href={`/pricing?region=${region}`}>See pricing</a>;
}

Decide the value once, on the server (from a cookie, a header or the route), and pass it down, so both renders use the same string:

pricing-link.tsx
export function PricingLink({ region }: { region: string }) {
  return <a href={`/pricing?region=${region}`}>See pricing</a>;
}

For ids, use useId instead of counters or random values.

Prevent it with ESLint

no-window-render-branch reports server/browser branches in render code, no-browser-global-in-render browser globals, and no-unstable-id ids built from random values, the clock or counters.

When the difference is intentional

suppressHydrationWarning covers an element's own attributes: React keeps the server value, and hydration-proof lists it as HP6001 (info). To stop comparing an attribute everywhere (tracking attributes, for example), list it in ignore.attributes. See ignoring findings.

Example

  ✖ /pricing 1.2s  1 error
    HP1002 Attribute differs between server and client  (browser-only api used during render, 59%)
      #pricing-link  in PricingLink
      attribute: href
      server: "/pricing?region=eu-west"
      client: "/pricing?region=us-east"
      components/pricing-link.tsx:5:10
      → Code like `typeof window !== "undefined"` or window/navigator/document access renders differently on the server. Read browser values in useEffect, or render the component on the client only.