Hydration Proof

Search documentation

Find a page or section

HP1008: Element only present in the server HTML

HP1008 (extra element): the server HTML contains a node the client render does not produce, so React discards that part of the page. Causes and the fix.

HP1008 (extra-element) means the server HTML contains an element or text node that React's first client render does not produce. React cannot match it, so it discards the server HTML of that part of the page and renders it again. Use the same condition on both sides instead of rendering server-only nodes.

CodeHP1008
Nameextra-element
Default severityError
GroupDOM mismatches
What it meansThe server HTML contains a node the client render does not produce.

What HP1008 (extra-element) means

A component rendered something on the server and nothing (or less) in the browser. The finding points at the parent element the node was in, and shows the node as the server value, with (absent) as the client value.

Scripts are not counted, and neither are <link>, <meta>, <style>, <title> and <base> tags in <head> (HP1014 covers the head). A whitespace-only text node on one side is HP1015, and an element the browser's parser moved because of invalid nesting is HP3001.

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: Did not expect server HTML to contain a <div> in <div>

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

Likely causes

  • A component that returns null in the browser when a stored flag is set: browser storage.
  • A server-only branch (typeof window === "undefined") or another browser-only API check.
  • A list that is shorter on the client because the client fetched it again: different data.
  • A media query that hides a section in the browser.

How to fix it

  • Do not render server-only nodes inside hydrated components; use the same condition on both sides.

A cookie notice that returning visitors never see in the browser, but always get in the server HTML:

cookie-notice.tsx
"use client";
 
export function CookieNotice() {
  if (typeof window !== "undefined" && localStorage.getItem("cookies-ok")) {
    return null;
  }
 
  return <p className="notice">We use cookies.</p>;
}

Render what the server rendered, then hide it after hydration:

cookie-notice.tsx
"use client";
 
import { useEffect, useState } from "react";
 
export function CookieNotice() {
  const [accepted, setAccepted] = useState(false);
 
  useEffect(() => {
    setAccepted(localStorage.getItem("cookies-ok") === "1");
  }, []);
 
  if (accepted) return null;
  return <p className="notice">We use cookies.</p>;
}

Better still, keep the choice in a cookie and read it on the server, so the notice is never sent to someone who accepted it.

Prevent it with ESLint

no-storage-in-initial-render and no-window-render-branch report the checks that make a component render less in the browser.

Example

  ✖ / 980ms  1 error
    HP1008 Element only present in the server HTML
      #footer  in SiteFooter
      server: "<p class=\"notice\">We use cookies.</p>"
      client: (absent)
      components/site-footer.tsx:6:5
      → Do not render server-only nodes inside hydrated components; use the same condition on both sides.

The selector and the source line are those of the parent, here the footer that renders CookieNotice. The HTML report shows the markup on both sides.