こんにちは。アドベントカレンダーを執筆していきます。
今回はフロントエンドのお話です。Java(Spring Boot)を使った開発を行った際、Thymeleaf(テンプレートエンジン)と Alpine.js(軽量フロントエンドライブラリ)を併用する機会がありました。
この組み合わせはそれほど珍しくないはずなのですが、記法の衝突や、テンプレート生成前後での挙動差などクセがあり、調べてもまとまった情報が少なかったため、備忘録としてまとめます。
本記事の対象者
- Thymeleaf と Alpine.js を併用しながらフロントエンド開発している方
- サーバーサイドテンプレート生成後に Alpine.js を適用したい方
- 記法の相性で混乱している方
なぜ癖があるのか?
結論から言うと、
Thymeleaf と Alpine.js が「コロン : 」を取り合うから
です。
さらに、
- Thymeleaf は サーバー側で HTML を生成
- Alpine.js は クライアント側でリアクティブに動作
と、HTML が生成されるタイミングも異なります。
このため、テンプレート内で JS 用の属性を動的に埋め込みたい場合に記法が混在し、特に x-data="" の中で Thymeleaf 変数を使うときに混乱しやすいです。
Thymeleafの基本
Thymeleaf はサーバーサイドで HTML を生成するテンプレートエンジンです。
特徴として:
-
th:*という属性を使って書く - 最終的に生成された HTML だけがユーザーへ送られる
-
${}の EL 式で値を埋め込む
例:
<div th:text="${user.name}"></div>
Alpine.jsの基本
Alpine.js はクライアント側で動作する軽量の挙動制御ライブラリです。
x-data
x-show
x-on:click
動的属性付与 :id="..." :value="..."
など、こちらも コロン : を多用します。
例:
<div x-data="{ open: false }">
<button x-on:click="open = !open">Toggle</button>
<p x-show="open">Hello</p>
</div>
これらのth:* の **:(コロン)**が、後述の Alpine.js の記法と衝突します。
また、x-data内にthymeleafからのデータを埋め込みたいときに記法が特殊になります。
本題: Thymeleafとalpine.jsの共存させるには?
ポイントは 2 つです。
① x-data をそのまま書くと Thymeleaf がコロンを解釈してしまう
→ th:x-data を使う
② x-data の中で Thymeleaf の変数を埋め込むときは |…| と ${} を併用
- Alpine.js のオブジェクト記法:コロン :
- Thymeleaf の埋め込み:${...}
- Thymeleaf の文字列囲い:| ... |
実例:li タグのループで値を Alpine 側に渡す場合
Java側でフラグメントを定義します
@RequiredArgsConstructor
@Controller
public class OrderController {
private final OrderService orderService;
@GetMapping("/")
public String getIndex(Model model) {
List<OrderList> orderList = orderService.getOrderList();
model.addAttribute("orderList", orderList);
return "pages/index :: orderList";
}
}
❌ NG:そのまま書くと壊れる
<li x-data="{ id: ${order.id} }" th:each="order : ${orderList}">
Thymeleaf と Alpine.js の両方が { id: ... } を解釈しようとしてエラーになります。
✅ OK:th:x-data と |...| と ${} を利用
<th:block th:fragment="orderList">
<li
th:each="order : ${orderList}"
th:x-data="|{ id: __${order.id}__ }|"
>
<!-- #省略 -->
</li>
</th:block>
ポイント解説
- th:x-data="| ... |"
→ Thymeleaf が中身を文字列として扱う - id: ${order.id}
→ Thymeleaf の EL 式 ${order.id} を生の値として埋め込む - 結果として、クライアントには次のように出力される:
<li x-data="{ id: 123 }">
さいごに
今回は Thymeleaf と Alpine.js を併用する際にハマりやすいポイントと、その記法の整理を紹介しました。
両方とも便利な技術ですが、**「コロン問題」や「サーバー生成前後の違い」**に気づかないとかなり混乱します。
同じ技術スタックで開発される方の参考になれば嬉しいです。