💡

【HTML】要素の入れ子ルールをやぶるとどうなるのか?

2023/07/08に公開

■ はじめに

<div>などのHTML要素ごとに入れ子にしていいものと、入れ子にしてはいけないものがあることはご存知でしょうか?

最近ブラウザやHTMLなどのWeb標準に備わる技術を学ぶなかで、HTML要素ごとの入れ子ルールがちゃんと定義されていることを知りました。

html
<div>ここに入れていいものと、入れてはダメなものがあるよ!</div>

「じゃあ、そのルール破ったらどうなるねん」 という疑問からこの記事を書いています。

通常のHTMLの例と私が普段利用しているNext.jsでの例を用いて説明していきます。
興味を持った方はぜひ最後まで読んでください。

■ 入れ子ルールとは?

この入れ子ルールのことを、Content modelといいます。
HTML StandardではContent model

A normative description of what content must be included as children and descendants of the element.
要素の子や子孫としてどのような内容を含めなければならないかを規範的に記述したもの。

と説明しています。

このContent modelのルールは1つ1つの要素ごとに決められており、HTML StandardMDNにも要素ごとのルールが記載されています。

そのルール付けは大きく

  • コンテンツカテゴリーを基準にルール付けされている要素
  • 複雑なルール付けがされている要素(コンテンツカテゴリーにくくられず)
    の2つに区分できそうです。

※ コンテンツカテゴリーとは(MDNより)

ほとんどの HTML 要素は 1 つ以上のコンテンツカテゴリーに属していて、カテゴリーごとに共通した特徴を持つ要素を分類しています。これは緩やかなグループ分けです(実際にはこれらのカテゴリーの要素間の関係を作成していません)。しかし、これらは、特にその複雑な詳細に遭遇したときに、カテゴリーの共有動作とその関連ルールを定義し記述するのに役立ちます。どのカテゴリーにも属さない要素も存在します。

◎ コンテンツカテゴリーを基準にルール付けされている要素

HTML Standard<h1~6>要素Content modelを確認してみます。

<h1~6>要素は非常にシンプルなルールとなっており、7つのコンテンツカテゴリーのうちPhrasing contentのみを許容します。

ただ、<h1~6>要素のように7つのコンテンツカテゴリーを基準にルール付けされているものは稀なようです。

◎ 複雑にルール付けされている要素

HTML Standard<table>要素Content modelを確認してみます。

なかなか複雑に書いてあるものの、
<caption>, <colgroup>, <thead>,<tbody>, <tr>, <tfoot>
を入れ子にしていいようです。

HTML Standard table

HTML Standardよりは信憑性が落ちそうなものの、MDNでも確認できます。

MDN <table>

■ 入れ子ルールをやぶるとどうなる?

では、気になる入れ子ルールをやぶるとどうなるのでしょうか?

結論として、原則としてエラー補正はされずにそのままDOMツリーに追加されるようです。
しかし、要素の種類によってはエラー補正がされてDOM構造が変換されることがあります。

それでは、
要素の種類によってはエラー補正がされてDOM構造が変換されることを例を用いて解説していきます。

<table>要素で試してみる

では、<table>内に<p>を入れてみます。
先程確認したように<table><p>を入れ子に許容しません。

html
<table>
  <tbody>
     <tr>
      <th>見出しのセル</th>
      <td>データのセル</td>
    </tr>
  </tbody>
  <!--    <table>タグの中に<p>タグはいれるのはルール違反だよ!-->
  <p>Pテキストだよ</p>
</table>

Google chrome で表示

構文エラーとなり、このようにブラウザで解釈されて表示されていることがわかります。

html
<p>Pテキストだよ</p>
<table>
  <tbody>
    <tr>
      <th>見出しのセル</th>
      <td>データのセル</td>
    </tr>
  </tbody>
</table>

<table>要素では、入れ子のルールをやぶると
エラー補正がされてDOM構造が変換されることが確認できました。

◎ HTML 解釈の仕様ルール

このようにDOM構造が変換されるようなHTML解釈の仕様ルールが定められているようです。
https://html.spec.whatwg.org/multipage/parsing.html

<table>要素の場合、不正な子孫要素が前に追い出される形になります。
この、不正な子孫要素が前に追い出されるのはfoster parentingというルールらしいです。

■ Next.js で入れ子ルールをやぶるとどうなる?

今度はNext.jsで入れ子ルールをやぶってみます。
先程の例と同じように<table><p>を例に試します。

package.json
// 動作確認実行環境
"next": "13.4.3",
"react": "18.2.0",
tsx
<table>
  <tbody>
     <tr>
      <th>見出しのセル</th>
      <td>データのセル</td>
    </tr>
  </tbody>
  {/* <table>タグの中に<p>タグはいれるのはルール違反だよ */}
  <p>Pテキストだよ</p>
</table>

するとDOM構造はマークアップしたものと変わらずに表示されるものの、エラーになりました。

error
Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.
Warning: Expected server HTML to contain a matching <p> in <table>.

See more info here: https://nextjs.org/docs/messages/react-hydration-error

エラー日本語訳

エラー:初期UIがサーバーでレンダリングされたものと一致しないため、Hydration に失敗しました。
ワーニング:サーバーのHTMLは<table>内に<p>を含むことを期待しました。

エラー文で案内されている、Next.jsのドキュメントにも、エラー発生理由の説明として下記内容が記載されています。

アプリケーションのレンダリング中に、事前レンダリングされた React ツリー (SSR/SSG) と、ブラウザーでの最初のレンダリング中にレンダリングされた React ツリーとの間に差異がありました。最初のレンダリングはReact の機能である Hydration と呼ばれます。

これにより、React ツリーが DOM と同期しなくなり、予期しないコンテンツ属性が存在する可能性があります。

ここから自己解釈
どうやら、React側ではマークアップのそのままを想定して Hydrationしようとした。
しかし、実際はブラウザで解釈された結果がそのマークアップと異なるため Hydrationに失敗するようです。

※Hydration, Hydrate とは
サーバー側で生成されたHTMLにJSの各ロジックを接続していくこと。
つまり静的な状態のHTML要素にJSのイベントハンドラなど接続していき、動的ウェブページに変換していく処理のこと。
クリックなどのイベントが発火されない静的な状態のUIをクリック可能な動的なUIにする処理。

SSRHydrationについても解説していますのでこちらの記事もよかったら読んでください。
https://zenn.dev/tm35/articles/0a64177c0a41bd

■ さいごに

Web標準の技術を学ぶことの重要性を実感しているところです。
深く学ぼうとするとやっぱり面白いです。

わかっていない部分、調べきれていない部分もあるので指摘やここはどうなん?という部分があればコメントください。

参考資料

Discussion