Skip to content

Bug: validateDOMNesting Hydration failed #24519

Closed
@icyJoseph

Description

@icyJoseph

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 text the 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Status: UnconfirmedA potential issue that we haven't yet confirmed as a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions