Hydration Proof

Search documentation

Find a page or section

HP1009: Element missing from the server HTML

HP1009 (missing element): React's client render produces a node the server HTML does not contain, so React re-renders that part. Causes and how to fix it.

HP1009 (missing-element) means React's first client render produces an element or text node that the server HTML does not contain. React cannot hydrate a node the server never sent, so it discards that part of the page and renders it again. Render client-only nodes after mount, or with a client-only component.

CodeHP1009
Namemissing-element
Default severityError
GroupDOM mismatches
What it meansThe client render produces a node the server HTML does not contain.

What HP1009 (missing-element) means

A component rendered more in the browser than on the server: a badge, a banner, a widget that depends on something only the browser knows. The finding points at the new node and shows it as the client value, with (absent) as the server value.

Scripts are not counted, and neither are <link>, <meta>, <style>, <title> and <base> tags added to <head> (HP1014 covers the head). A whitespace-only text node is HP1015, and an element a script or extension inserted before hydration is reported as a change outside React (HP4001).

The React error it matches

Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client.
Warning: Expected server HTML to contain a matching <div> in <div>

The first is React 19, the second the React 18 development warning. See Expected server HTML to contain a matching.

Likely causes

How to fix it

  • Render client-only nodes after mount (useEffect) or with a client-only component.

A cart badge that only exists in the browser:

cart-badge.tsx
"use client";
 
export function CartBadge() {
  const count =
    typeof window === "undefined"
      ? 0
      : Number(localStorage.getItem("cart-count") ?? 0);
 
  return (
    <a href="/cart" id="cart">
      Cart
      {count > 0 && <b className="badge">{count}</b>}
    </a>
  );
}

Read the count after hydration, so the first client render matches the server:

cart-badge.tsx
"use client";
 
import { useEffect, useState } from "react";
 
export function CartBadge() {
  const [count, setCount] = useState(0);
 
  useEffect(() => {
    setCount(Number(localStorage.getItem("cart-count") ?? 0));
  }, []);
 
  return (
    <a href="/cart" id="cart">
      Cart
      {count > 0 && <b className="badge">{count}</b>}
    </a>
  );
}

For a whole component that cannot render on the server, see client-only components and next/dynamic with ssr: false.

Prevent it with ESLint

no-storage-in-initial-render, no-window-render-branch and no-match-media-in-render report the reads that make a component render more in the browser.

Example

  ✖ /shop 1.2s  1 error
    HP1009 Element missing from the server HTML  (localstorage / sessionstorage read during render, 85%)
      #cart > b  in CartBadge
      server: (absent)
      client: "<b class=\"badge\">2</b>"
      components/cart-badge.tsx:12:21
      → Storage is only available in the browser. Render a neutral value first and read storage in useEffect.