Hydration Proof

Search documentation

Find a page or section

eslint-plugin-validate-jsx-nesting vs hydration-proof

Two linters for HTML the browser rewrites, with different reach.

The ESLint no-invalid-jsx-nesting rule, from eslint-plugin-validate-jsx-nesting, reports HTML nesting the browser would repair, such as a <p> inside a <p>, when the child tag is written directly inside the parent. hydration-proof's no-invalid-interactive-nesting looks through fragments, conditions and lists and at any depth, and explains the hydration error each case causes.

At a glance

no-invalid-jsx-nestingno-invalid-interactive-nesting
ChecksA tag and its direct parent tagA tag and every enclosing tag in the same component
Through fragments, &&, ? :, arrays, .map()NoYes
<div> inside <span> inside <p>Not reportedReported
<a> in <a>, <button> in <button>, <form> in <form>Direct childrenAt any depth
Interactive content in <a> or <button>Not checkedinput, select, textarea, label, iframe and more
Table structure<tr> outside a table section, <td> outside <tr>, and more<tr> directly in <table>, <td> directly in a table section
Headings in headings, <li> in <li>, <select> and <head> contentCheckedNot checked
Across componentsNoNo; hydration-proof test finds those
Server ComponentsCheckedChecked, even with the next preset
MessageInvalid HTML nesting: <div> can not be child of <p>What the parser does and how hydration fails
Latest release0.1.1, April 20231.0

eslint-plugin-react, the other common React linter, has no rule for DOM nesting.

What ESLint no-invalid-jsx-nesting does well

The plugin has one rule and does it simply:

  • A broad list of content rules. It uses the validate-html-nesting library, which knows the only valid children of <table>, <tr>, <select>, <head> and <colgroup>, elements such as <textarea> and <option> that take no element children, and pairs like a heading in a heading or <li> in <li>. Some of these are outside what hydration-proof's rule checks.
  • Framework agnostic. It works on any JSX, not only React.
  • Small. Its only dependency is validate-html-nesting.

It has about 42,000 weekly downloads on npm.

Where hydration-proof differs

It looks past the direct parent. no-invalid-jsx-nesting compares a tag with its parent only when both are HTML tags written next to each other. These all break hydration and are not reported by it:

components/intro.tsx
export function Intro({
  items,
  showMore,
}: {
  items: string[];
  showMore: boolean;
}) {
  return (
    <p>
      Welcome.
      {/* Inside a condition: the parent of <div> is not the <p> tag. */}
      {showMore && <div>Read more below.</div>}
      {/* One level deeper: the browser still closes the <p>. */}
      <span>
        <ul>
          {items.map((item) => (
            <li key={item}>{item}</li>
          ))}
        </ul>
      </span>
    </p>
  );
}

no-invalid-interactive-nesting follows fragments, &&, ? :, arrays and .map() callbacks, and walks every enclosing tag in the component, stopping where the HTML parser would. It reports both cases above.

It covers interactive content. A <button>, <input> or <select> inside a link, or a link inside a button, is reported with the reason. See HP3002 for what the CLI reports.

It explains the hydration error. Each message says what the browser does with the markup and how that breaks hydration, for example that the browser closes the <p> before the <div>, so the DOM no longer matches what React rendered.

It is part of a hydration rule set. The same plugin has 14 more rules for the clock, random values, locale, timezone, storage and browser APIs (all rules).

Neither linter sees nesting across components: <p><Card /></p> where Card renders a <div>. hydration-proof test checks the real server HTML with React's own nesting rules and reports it as HP3001, with the line in the server HTML.

Use both

They overlap on the common cases, like a <div> written directly inside a <p>, which then gets two reports. Each also catches something the other does not, so running both is reasonable:

eslint.config.js
import hydrationProof from "eslint-plugin-hydration-proof";
import validateJsxNesting from "eslint-plugin-validate-jsx-nesting";
 
export default [
  hydrationProof.configs.recommended,
  {
    plugins: { "validate-jsx-nesting": validateJsxNesting },
    rules: { "validate-jsx-nesting/no-invalid-jsx-nesting": "error" },
  },
];
npm install -D eslint-plugin-hydration-proof eslint-plugin-validate-jsx-nesting

Sources

As of September 2026: