React の Hydration エラーと HTML のコンテンツモデル違反
Hydration エラー
Next.js を使って開発していると、以下のような Hydration エラーが発生することがある。
Text content does not match server-rendered HTML
サーバーサイドでレンダリングされた HTML とブラウザでレンダリングされた HTML に差異が発生している場合に起きる。
差異が発生する理由はいくつかあるが、HTML のコンテンツモデル違反によりブラウザが HTML を補完している場合がある。
ブラウザが HTML を補完する
いくつかパターンがあるので列挙する。
親子関係の違反
サーバーで以下の HTML を生成する。
<p>
<div>test</div>
</p>
これをブラウザが補完するとこうなる。
<p></p> <!-- 終了タグが追加されている -->
<div>test</div>
<p></p>
p タグの子要素にはフレージング・コンテンツのみが許可されているが、div タグはフレージング・コンテンツではない。 したがってこれはコンテンツモデルに違反していることになるので、ブラウザが違反していない形に補完している。
他にも例として、foster parenting と呼ばれる仕様がある。
サーバーで以下の HTML を生成する。
<table>
<p>test</p>
</table>
これをブラウザが補完すると以下になる。
<p>test</p> <!-- 追い出されている -->
<table></table>
table タグの子要素に p タグを配置するのはコンテンツモデル違反のため、p タグが外側に移動する。 ちなみに foster parent は里親・育ての親という意味らしく、子要素を別の親要素に託す仕様の比喩と思われる。
終了タグの補完
サーバーで以下の HTML を生成する。
<p>
<b>
<b>
<b>
<b>4
これをブラウザが補完すると以下になる。
<p>
<b>
<b>
<b>
<b>4</b>
</b> <!-- 終了タグが追加されている -->
</b>
</b>
</p>
ちなみに、終了タグの補完についてはノアの方舟(Noah's Ark) と呼ばれる補完仕様が存在する。1 同一要素の開始タグが 4 つ以上ある場合に、2 番目以降の子に対しては終了タグに対応するタグが 3 つまでしか補完されない。
サーバーで以下の HTML を生成する。
<p>
<b>
<b>
<b>
<b>
4</p>
3 <!-- 2番目 -->
ブラウザが補完すると以下になる。直前に b タグが 4 つあるが 3 つまでしか補完されない(残りは無視される)
<!-- 省略 -->
<b>4</b>
</b>
</b>
</b>
</p>
<b>
<b>
<b>3</b>
</b>
</b> <!-- 終了タグの補完は 3 つだけ -->
まとめ
ブラウザは HTTP レスポンスの HTML をそのままパースするのではなく、いくつかの仕様に沿って HTML を改変することがある。 サーバーサイドレンダリング時に、コンテンツモデル違反に該当する HTML を生成すると Hydration エラーに繋がる場合があるので 注意しよう。
Footnotes
-
太田 良典, 中村 直樹 (2022). HTML 解体新書 ボーンデジタル ↩