Hydration Proof

Search documentation

Find a page or section

HP1006: Attribute missing from the server HTML

HP1006 (missing attribute): React renders an attribute on the client that the server HTML lacks, and hydration never adds it. The causes and how to fix it.

HP1006 (missing-attribute) means React renders an attribute on the client that the server HTML does not have. React does not add it during hydration, so the element goes without it until it re-renders: a link without its target, a control without its aria-* state. Render the attribute on the server as well, or add it after mount.

CodeHP1006
Namemissing-attribute
Default severityError
GroupDOM mismatches
What it meansReact renders an attribute on the client that the server HTML does not have.

What HP1006 (missing-attribute) means

The element exists on both sides, but one attribute is only in React's client props. The component computed it from something only the browser has, so the server rendered undefined and left it out.

React does not patch attributes during hydration, and React 19 production builds report nothing. hydration-proof compares every reused element's attributes with the props React renders for it, so it finds the missing attribute in production too. A missing class or style is reported as HP1004 or HP1003.

The React error it matches

In development, React 19 prints:

A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up.

See attributes didn't match.

Likely causes

How to fix it

  • Render the attribute on the server as well, or add it after mount in useEffect.
share-button.tsx
"use client";
 
export function ShareButton() {
  const touch = typeof window !== "undefined" && "ontouchstart" in window;
 
  return (
    <button type="button" data-input={touch ? "touch" : undefined}>
      Share
    </button>
  );
}

Start from the value the server can render, and switch after hydration:

share-button.tsx
"use client";
 
import { useEffect, useState } from "react";
 
export function ShareButton() {
  const [touch, setTouch] = useState(false);
 
  useEffect(() => {
    setTouch("ontouchstart" in window);
  }, []);
 
  return (
    <button type="button" data-input={touch ? "touch" : undefined}>
      Share
    </button>
  );
}

Prevent it with ESLint

no-browser-global-in-render and no-window-render-branch report the browser reads and branches that cause it.

When the difference is intentional

suppressHydrationWarning on the element keeps the server's attributes without a warning; hydration-proof then lists the difference as HP6001 (info). ignore.attributes stops comparing an attribute everywhere. See ignoring findings.

Example

  ✖ /posts/42 1.1s  1 error
    HP1006 Attribute missing from the server HTML  (browser-only api used during render, 60%)
      article > button  in ShareButton
      attribute: data-input
      server: (absent)
      client: "touch"
      components/share-button.tsx:7:5
      → 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.