ThymeleafのFragmentで迷いやすいth:insertとth:replaceは、ホスト要素を残すかどうかで区別できます。th:insertは指定したFragmentをホストタグの中へ挿入します。th:replaceはホストタグ自体をFragmentで置き換えます。「共通ヘッダーを入れたらdivが一段増えた」という場合は、まずこの出力差を確認すると原因を切り分けやすくなります。
insertはホストを残し、replaceは置き換える
| 属性 | ホスト要素 | Fragment | 向いているケース |
|---|---|---|---|
| th:insert | 残る | ホスト要素の子として入る | 既存のラッパーを維持したい |
| th:replace | 消える | Fragment自身がその位置へ入る | Fragment側のタグ構造をそのまま使いたい |
どちらが常に正解というものではありません。最終的に残したいHTML構造から逆算して選びます。
同じFragmentでも生成後のDOMが変わる
まず共通Fragmentをfooter.htmlへ定義します。
<footer th:fragment="copy">
<p>Copyright Route Zero</p>
</footer>
呼び出し側でinsertとreplaceをそれぞれ使います。
<div th:insert="~{footer :: copy}"></div>
<div th:replace="~{footer :: copy}"></div>
テンプレート処理後の構造は次のように異なります。insertでは呼び出し側のdivが残り、その中へfooterが入ります。replaceでは呼び出し側のdivが消え、footerがその位置へ入ります。
<!-- th:insert の結果 -->
<div>
<footer>
<p>Copyright Route Zero</p>
</footer>
</div>
<!-- th:replace の結果 -->
<footer>
<p>Copyright Route Zero</p>
</footer>
タグが二重になったらinsertのホストを確認する
Fragment側がheaderやfooterなど意味のあるタグを持ち、呼び出し側にも同じ役割のラッパーを置いていると、insertでは階層が一つ増えます。それ自体は仕様どおりですが、CSSセレクタやFlexbox/Gridの対象が変わる原因になることがあります。
- 呼び出し側のホストタグを残す必要があるか確認する
- Fragment自身がheader・footer・navなどの完成した構造を持つならreplaceを検討する
- ホスト側にclassやレイアウト責務があるならinsertを検討する
- 生成後HTMLをブラウザの開発者ツールで確認し、想定した階層になっているかを見る
Fragmentを指定する基本構文
<header th:replace="~{fragments/header :: siteHeader}"></header>
この例ではfragments/headerテンプレート内のsiteHeader Fragmentを参照します。Thymeleaf 3.1の公式チュートリアルでは、~{template :: fragment}の形でFragment式を記述できます。
用語解説:Fragment
ヘッダー、フッター、ナビゲーションなど、複数テンプレートから再利用するHTML断片です。
insertとreplaceの判断手順
- 最終的に残したいHTMLタグ構造を先に決める
- 呼び出し側のホスト要素を残したいならinsert
- Fragment自身のルートタグをその位置へ置きたいならreplace
- CSSやJavaScriptが特定のDOM階層へ依存している場合は生成後HTMLで確認する
属性名だけを暗記するより、「ホスト要素が残るか消えるか」を基準にすると、共通化後のDOMを予測しやすくなります。
関連するThymeleafの基本を確認する
Fragment以前の基本構文から確認したい場合はThymeleaf入門、${…}・@{…}・*{…}の違いは式の解説、テンプレートが読み込めない場合はParse Errorの記事が参考になります。
(Thymeleafの基本構文から確認するについては『Thymeleaf入門│Spring Bootで実践する基本サンプル』をご参照ください)
(Thymeleaf式の違いを確認するについては『Thymeleafで「$」「@」「*」はどう違う?使い分けガイド』をご参照ください)
(テンプレートの構文エラーを切り分けるについては『Thymeleaf Parse Errorの原因と対処法』をご参照ください)
よくある疑問
共通ヘッダーにはreplaceを使えばよい?
Fragment自身がheaderタグを持ち、そのタグをそのまま配置したいならreplaceが分かりやすいです。呼び出し側のラッパーを残す必要があるならinsertを選びます。
th:insertを使うと必ずdivが増える?
ホストがdivならそのdivは残ります。Fragmentがその内側へ挿入されるためです。ホストタグが何かによって生成後の構造は変わります。
th:replaceはFragmentの中身だけを入れる?
いいえ。th:replaceはホストタグを指定Fragmentそのもので置き換えます。Fragmentのルート要素も結果に含まれます。
まとめ
th:insertはホスト要素を残してFragmentを子として挿入し、th:replaceはホスト要素ごとFragmentへ置き換えます。タグが一段増えた、CSSが想定とずれたというときは、テンプレートだけでなく生成後DOMを確認してください。残したいHTML構造を先に決めれば、insertとreplaceを選びやすくなります。
まず既存のFragmentを一つ選び、ブラウザの開発者ツールでinsert/replace後のDOM階層を確認してみてください。