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 the markup you inject with dangerouslySetInnerHTML is different on the server and on the client. React does not patch it during hydration, so the page keeps the server's version, and React 19 production builds say nothing. Make the injected HTML identical on 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  (browser-only api used during render, 60%)
      #post-body  in PostBody
      The markup injected with dangerouslySetInnerHTML differs from what the client renders.
      components/post-body.tsx:7: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.