Skip to content

Understanding Hydration Mismatches

Jason Miller edited this page Jun 9, 2022 · 1 revision

Preact does not currently warn about hydration mismatches, and that's partly by-design.

One reason for this is that Preact has two hydration modes: The first is the normal hydrate() hydration, which tries to avoid ever mutating the DOM tree being hydrated. The second is something called "mutative hydration", which happens when render() is called with a third argument providing the root element for an existing DOM tree. During mutative hydration, Preact attempts to gracefully handle mismatches between the Virtual DOM tree and the existing DOM tree. It uses the same diffing logic that handles normal client-side updates, and attempts to update mismatched attributes and out-of-order elements.

As a result, it's difficult to determine what constitutes a hydration mismatch "error", because in many cases the behavior of a mismatch is consistent and/or desirable.

Here are some examples of hydration mismatches that can be desirable:

Text

When the HTML being hydrated contains text that is different from the text rendered by client-side components, the HTML's version of the text is left in-place, and the DOM structure will still be properly hydrated. A subsequent render can update this text. This also works for adjacent Text nodes, despite there being no representation of adjacent Text nodes in HTML (Preact internally handles these differences)

Appended Elements

When the Virtual DOM tree contains elements not present in the HTML, Preact may not be able to determine which DOM element should be hydrated by which component (as components and DOM elements have a many-to-many relationship). However, when "extra" elements in the Virtual DOM tree are the last elements within a given DOM parent node, Preact can safely render and insert these without affecting the rest of the hydration process. This can be particularly useful for things like client-side modals or scripts.

Removed Elements

Preact hydrates the DOM in its natural order (depth-first, first-to-last). When the Virtual DOM contains an Element that is not the next expected element in that order, Preact skips over each sibling element in the DOM until it finds one that matches the type of the Virtual DOM element. For this reason, when the DOM contains nodes that the Virtual DOM does not contain, those DOM nodes are simply removed and hydration continues as if they were never present.

Debugging Hydration Mismatches

It's possible to track where and why hydration mismatches occur by wrapping hydration in a sort of "middleware", using either MutationObserver of Preact's Option Hooks. To do this, we'll track any DOM mutations that are performed during the synchronous call to Preact's hydrate() method. Then we can filter the list of mutated nodes to include only the highest nodes in the DOM tree, since subtrees of a mutated node are part of the same hydration mismatch.

The following Gist is a quick prototype of hydration mismatch tracking using MutationObserver: Preact Hydration Mismatch Tracker

It returns an Array of hydration mismatches, each with a reason property indicating what happened to cause the mismatch (attribute change, added or removed element), as well as the DOM element where the mismatch occurred.