# 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.

Source: https://hydration.jscrate.dev/docs/issues/hp1002
Last updated: 2026-09-18

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.

| | |
| --- | --- |
| Code | `HP1002` |
| Name | `attribute-mismatch` |
| Default severity | Error |
| Group | DOM mismatches |
| What it means | An 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:

| Difference                           | Code                          |
| ------------------------------------ | ----------------------------- |
| A value on both sides, but different | HP1002                        |
| `style`                              | [HP1003](https://hydration.jscrate.dev/docs/issues/hp1003) |
| `class`                              | [HP1004](https://hydration.jscrate.dev/docs/issues/hp1004) |
| Only in the server HTML              | [HP1005](https://hydration.jscrate.dev/docs/issues/hp1005) |
| Only in React's client render        | [HP1006](https://hydration.jscrate.dev/docs/issues/hp1006) |

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:

```text
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](https://hydration.jscrate.dev/docs/errors/tree-hydrated-but-attributes-didnt-match)
and [Prop `className` did not match](https://hydration.jscrate.dev/docs/errors/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](https://hydration.jscrate.dev/docs/causes/browser-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](https://hydration.jscrate.dev/docs/causes/theme): an SVG `fill` chosen with
  `prefers-color-scheme`, as on the `/svg-attr` test page.
- A [generated id](https://hydration.jscrate.dev/docs/causes/unstable-id) from a counter or a random value
  in `id`, `for` or `aria-*` attributes.
- A [time-dependent value](https://hydration.jscrate.dev/docs/causes/time) in `datetime` or `title`, or a
  [random value](https://hydration.jscrate.dev/docs/causes/random) 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:

```tsx title="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:

```tsx title="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`](https://hydration.jscrate.dev/docs/rules/no-window-render-branch) reports
server/browser branches in render code,
[`no-browser-global-in-render`](https://hydration.jscrate.dev/docs/rules/no-browser-global-in-render)
browser globals, and [`no-unstable-id`](https://hydration.jscrate.dev/docs/rules/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](https://hydration.jscrate.dev/docs/issues/hp6001)
(info). To stop comparing an attribute everywhere (tracking attributes, for
example), list it in `ignore.attributes`. See
[ignoring findings](https://hydration.jscrate.dev/docs/ignoring).

## Example

```text
  ✖ /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.
```

## Related

- [HP1001: text differs](https://hydration.jscrate.dev/docs/issues/hp1001)
- [HP1003: inline style differs](https://hydration.jscrate.dev/docs/issues/hp1003)
- [HP1004: class name differs](https://hydration.jscrate.dev/docs/issues/hp1004)
- [Why some errors only appear in production](https://hydration.jscrate.dev/docs/guides/hydration-error-only-in-production)
- [Browser-only APIs during render](https://hydration.jscrate.dev/docs/causes/browser-api)
