react: Bug: `validateDOMNesting` Hydration failed
Having an invalid DOM structure, normally triggers validateDOMNesting, but when combined with SSR, this also triggers Hydration failed, and There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
I assume the above is a combination of two things, and error being raised by React, and Next.js not handling it well.
The React Error, as far as I know
React version: 17.0.2, 18.0.0 and 18.1.0.
The current behaviour
In SSR frameworks such as Next.js, an error raises claiming that Hydration failed because the initial UI does not match what was rendered on the server..
With React 18, and Next.js, this, in turn, triggers: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering. I assume, Next.js is somehow not handling the invalidDOMNesting error, or the Hydration failed error, and that is probably something they ought to fix.
Found this bit on the code as well:
  // This validation code was written based on the HTML5 parsing spec:
  // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope
  //
  // Note: this does not catch all invalid nesting, nor does it try to (as it's
  // not clear what practical benefit doing so provides); instead, we warn only
  // for cases where the parser will give a parse tree differing from what React
  // intended. For example, <b><div></div></b> is invalid but we don't warn
  // because it still parses correctly; we do warn for other cases like nested
  // <p> tags where the beginning of the second element implicitly closes the
  // first, causing a confusing mess.
  // https://html.spec.whatwg.org/multipage/syntax.html#special
Edit 2023
I’ve now gathered enough info and experience from helping out folks that run into this issue.
First the initial example I had provided is a weak case. Nested <p> tags are assumed by HTML interpreters as a mistake and render them sequentially instead. The react-dom/server rendering behaviour is correct. A structure such as :
<p class="outer">
    <p class="inner">hello</p>
</p>
Is rendered by the browser as:
<p class="outer"> </p>
<p class="inner">hello</p>
<p></p>
However, if you do this programmatically:
const outer = document.createElement('p')
const inner = document.createElement('p')
outer.classList.add("outer")
inner.classList.add("inner")
outer.append(inner)
document.body.append(outer)
Then the DOM is not corrected by the browser and it renders:
<p class="outer"><p class="inner"></p></p>
Another case where the browser, or rather HTML interpreter I guess, changes the received server HTML:
 <table>
   <tr>
     <th>a</th>
     <th>b</th>
     <th>b</th>
   </tr>
   <tr>
 </table>
The above is changed by the browser to:
<table>
  <tbody>
    <tr>
      <th>a</th>
      <th>b</th>
      <th>b</th>
    </tr>
    <tr></tr>
  </tbody>
</table>
However, when React does hydration it’ll render a table without tbody, and that causes a mismatch with what it finds.
Second, while it is annoying, a hydration error produced of invalid DOM nesting is one of the easiest to fix of its kind. The error log often points at where the divergence has occurred!
Third, in the face of validateDOMNesting errors, there are a few things that could help you figure out the error. I often follow this approach:
- Collect information about the error, what is the error saying it found, and what did it expect?
- Perhaps you can reproduce it locally?
Catch the browser making changes to the server sent HTML:
- Inspect the HTML sent by the server, for example in view-source:https://your-site.your-domain, save this and take it to a text editor
- Disable JavaScript and load your page, does your UI look different?
- In the Chrome dev tools, with JS disabled, edit as textthe entire HTML document, uglify it and take it to a text editor
- Compare the two pieces of text you’ve saved to text editors
Although the above could probably be automated somehow 😉
Are you making wrong assumptions about how HTML works? One resource I’ve used a lot to avoid this kind of issue is: https://caninclude.glitch.me/
And I think that’s as far as you can go with validateDOMNesting kind of errors. There’s other kind of errors, such as hydration mismatch because you straight up return different DOM, like:
const Trouble = () => typeof window === 'undefined' ? <div>server</div> : <div>client</div>
With that being said I am ready to close this issue.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 5
- Comments: 20 (5 by maintainers)
This seems to be expected behavior? SSR cannot work with improper tag nesting as per the HTML spec, the browser’s parser won’t accept it, and the whole point of SSR is to have the browser parse the initial document structure for you without using any JS. Client side rendering can work with improper tag nesting because the DOM apis do allow creating these invalid nestings “by hand” (with
document.createElement,appendChild, etc.).There is no way to create a raw HTML document that nests a
<p>directly inside a<p>using static markup, so there’s no way to do it with SSR.So this situation is a warning for client side rendering, an error for SSR, and a warning for hydration; but the only way to continue the “hydration” is to blast away all of the DOM so far because it will be wrong due to invalid nesting.
This is one of the weak points of Next.js. Not communicating the problem well with the developers. Next.js has an extremely poor DX regarding errors.
I also expected it to tell me why the hydration failed and what element it thought was incorrect. In a real-world application, how am I supposed to find the incorrect nesting? I don’t get any warnings in my local development at all. And after deploying the built version to my server, I got this vague error. Now I can’t find what element is nested incorrectly.
Hey! To circle back here, this is a React specific error which logged a couple more lines in the development console. Next.js caught the first part of it and was not something we had control over. Last month @hanneslund on our team dug into this and improved the overlay in a bunch of ways when using the
appdirectory. This is not available inpagesyet as we wanted to make sure the approach is stable first but we can backport it forpages.Notable changes:
Old
New
Toast:
I would expect SSR on the server produce a server-console warning that it encountered invalid html nesting and modified html output and a remark that this could cause hydration errors on the client side.
Yeah, I mostly agree, and after going through very long discussion threads, it does seem that it is developers fault for not fixing warnings/errors… However, it might be worth having clearer communication with regards to the implications of invalid DOM nesting, maybe it’s already there and I just haven’t seen it.
I’m currently trying to fix this issue by deleting several files at a time from my project until I locate the culprit. There must be a better way to locate the issue?
Would be great to improve this error reporting for
validateDOMNesting/Hydration failed! Would be great to get a visual diff of the HTML that should change:Visual HTML Diff for
validateDOMNestingImproving the existing message / overlay: Showing a visual HTML diff (server rendered HTML vs client rendered HTML) in the error message would be very helpful for creating actionable error messages, as I also wrote in my Next.js issue. Eg something like:
React providing enough metadata about the HTML related to the error to construct such a diff would be useful for multiple frameworks.
Is a lint or similar that can identify this issue? As others have pointed out, it’s quite difficult without better tooling.
A funny thing about the error is that it appears when the page is translated in development.
Another update on a lint rule, cross-posting my comment from https://github.com/jsx-eslint/eslint-plugin-react/issues/3310#issuecomment-1460653623:
Using some of the original
validateDOMNestingcode from @sophiebits and thesrc/mapping.jsfile from @MananTank’svalidate-html-nesting, I was able to come up with a simple set of rules to prevent most footguns for our students using ESLint’sno-restricted-syntaxrule (uses@typescript-eslint/utilsfor the config type at the first line):(This has also been released as part of
@upleveled/eslint-config-upleveled@3.13.0).eslintrc.cjsThis handles the things we see that students most commonly need:
table,thead,tbody,tfoot,tr,th,tdol,ul,lipand common elements which are not allowedaelements,buttonelements and frameworkLinkcomponents inside of each otherLooks like this on some invalid code:
@maxcountryman in the meantime, before the error message is improved, there are some tooling things here:
eslint-plugin-reacthere: https://github.com/jsx-eslint/eslint-plugin-react/issues/3310This is going to help a ton. There are a couple of more rules that could be added, how can I contribute?