Hydration Proof

Search documentation

Find a page or section

HP1013: dangerouslySetInnerHTML differs between server and client

HP1013 (inner HTML mismatch): the HTML injected with dangerouslySetInnerHTML differs between server and client, and React never patches it. Causes and fix.

HP1013 (inner-html-mismatch) means dangerouslySetInnerHTML produced different markup on the server and in the browser. React keeps the server's version during hydration; React 19 production builds may not warn you. Send the same HTML to both sides, or render it after mount.

CodeHP1013
Nameinner-html-mismatch
Default severityError
GroupDOM mismatches
What it meansThe HTML injected with dangerouslySetInnerHTML differs between the server and the client. React does not patch it.

What HP1013 (inner-html-mismatch) means

The element exists on both sides, but the __html string React renders on the client does not produce the same content as the server HTML. hydration-proof parses the client's string in an inert document and compares the result with the element's content, so markup that parses to the same thing (<br/> and <br>, for example) is not reported.

The finding has no single server or client value; the HTML report shows both versions of the element side by side.

The React error it matches

React 18 warns about it in development:

Warning: Prop `dangerouslySetInnerHTML` did not match.

See Prop className did not match, which covers this variant of the warning too. React 19 production builds report nothing.

Likely causes

  • HTML built behind a typeof window check, or processed only in the browser (sanitized, highlighted, rewritten): a browser-only API. The package's /dangerous-html test page injects different markup on each side this way.
  • A string that contains a time or a number formatted with the runtime's locale.
  • Content the client fetched again: different data.

How to fix it

  • Make the injected HTML identical on server and client, or render it after mount.

Processing the HTML only in the browser gives the two renders different strings:

post-body.tsx
"use client";
 
import { highlight } from "./highlight";
 
export function PostBody({ html }: { html: string }) {
  const content = typeof window === "undefined" ? html : highlight(html);
  return <div id="post-body" dangerouslySetInnerHTML={{ __html: content }} />;
}

Do the processing once, where the data is loaded (on the server or at build time), and pass the finished string to both renders:

post-body.tsx
export function PostBody({ html }: { html: string }) {
  return <div id="post-body" dangerouslySetInnerHTML={{ __html: html }} />;
}

Prevent it with ESLint

no-window-render-branch reports the server/browser branch, and no-date-in-render and no-locale-without-explicit-locale the values that change the string.

Example

  ✖ /blog/launch 1.0s  1 error
    HP1013 dangerouslySetInnerHTML differs between server and client  ·  components/post-body.tsx:7
      The markup injected with dangerouslySetInnerHTML differs from what the client renders.
      Possible cause: Browser-only api used during render.  Fix: Code like `typeof window !== "undefined"` or window/navigator/document access renders differently on the server. Read browser values in useEffect, or render th…