この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:5
ページ更新者:atom
更新日時:2026-06-11 07:10:02

タイトル: Thymeleaf
SEOタイトル: Thymeleaf 入門: Spring Boot 標準テンプレートの記法・レイアウト・JSP との違い

この記事の要点
  • Thymeleaf は Spring Boot 標準のサーバーサイドテンプレートエンジン
  • HTML として開けるのが最大の特徴。デザイナーがそのまま編集可能
  • 基本属性: th:text / th:if / th:each / th:href
  • レイアウト: th:fragment + th:replace / th:insert
  • Spring MVC との連携: model.addAttribute("user", user)${user.name}

Thymeleaf とは

Thymeleaf は Spring Boot 標準のテンプレートエンジン。最大の特徴は th: 属性で記述するため素の HTML としてブラウザで開けること。デザイナーがそのまま編集できるため、JSP に比べて開発フローが分業しやすい。

項目ThymeleafJSP
HTML として開ける×
Spring Boot 標準△ (要設定)
記法HTML 属性 th:*カスタムタグ <c:if>
レイアウトfragment / decoratorinclude / sitemesh
学習コスト低(HTML が分かれば)

セットアップ (Spring Boot)

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
src/main/resources/
├── templates/        ← Thymeleaf テンプレート (デフォルト)
│   ├── index.html
│   └── user/
│       ├── list.html
│       └── detail.html
└── static/           ← CSS / JS / 画像
    ├── css/app.css
    └── js/app.js

基本記法

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="${title}">タイトル</title>
    <link rel="stylesheet" th:href="@{/css/app.css}">
</head>
<body>

<!-- テキスト埋め込み -->
<h1 th:text="${user.name}">山田太郎</h1>

<!-- HTML として埋め込み (XSS 注意) -->
<div th:utext="${user.bio}">紹介文</div>

<!-- 属性値 -->
<a th:href="@{/user/{id}(id=${user.id})}">詳細</a>
<img th:src="@{/img/avatar.png}" th:alt="${user.name}">

<!-- 条件分岐 -->
<p th:if="${user.admin}">管理者です</p>
<p th:unless="${user.admin}">一般ユーザーです</p>

<!-- if / else 相当: switch -->
<div th:switch="${user.role}">
    <p th:case="'ADMIN'">管理者</p>
    <p th:case="'USER'">ユーザー</p>
    <p th:case="*">ゲスト</p>
</div>

<!-- 繰り返し -->
<ul>
    <li th:each="u : ${users}" th:text="${u.name}">名前</li>
</ul>

<!-- インデックスや状態取得 -->
<tr th:each="u, stat : ${users}"
    th:class="${stat.odd}? 'odd' : 'even'">
    <td th:text="${stat.count}">1</td>
    <td th:text="${u.name}">名前</td>
</tr>

</body>
</html>

フォーム

<form th:action="@{/user/save}" th:object="${form}" method="post">

    <!-- フィールドバインド -->
    <input type="text" th:field="*{name}" />
    <span th:if="${#fields.hasErrors('name')}"
          th:errors="*{name}">エラー</span>

    <input type="email" th:field="*{email}" />

    <!-- ラジオ -->
    <input type="radio" th:field="*{gender}" value="M" />男
    <input type="radio" th:field="*{gender}" value="F" />女

    <!-- セレクト -->
    <select th:field="*{prefecture}">
        <option value="">選択してください</option>
        <option th:each="p : ${prefectures}"
                th:value="${p.code}"
                th:text="${p.name}">県名</option>
    </select>

    <!-- チェックボックス -->
    <input type="checkbox" th:field="*{terms}" />利用規約に同意

    <button type="submit">送信</button>
</form>

Spring MVC との連携

@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping
    public String list(Model model) {
        model.addAttribute("users", userService.findAll());
        model.addAttribute("title", "ユーザー一覧");
        return "user/list";   // → templates/user/list.html
    }

    @GetMapping("/new")
    public String form(Model model) {
        model.addAttribute("form", new UserForm());
        return "user/edit";
    }

    @PostMapping("/save")
    public String save(@Valid @ModelAttribute("form") UserForm form,
                       BindingResult result,
                       RedirectAttributes ra) {
        if (result.hasErrors()) return "user/edit";

        userService.save(form);
        ra.addFlashAttribute("message", "保存しました");
        return "redirect:/user";
    }
}

レイアウト (fragment)

共通ヘッダ・フッタを切り出して再利用:

<!-- templates/fragments/layout.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>共通</title>
</head>
<body>

<header th:fragment="header">
    <h1>サンプルサイト</h1>
    <nav>
        <a th:href="@{/}">ホーム</a>
        <a th:href="@{/user}">ユーザー</a>
    </nav>
</header>

<footer th:fragment="footer">
    <p>&copy; 2026 Example</p>
</footer>

</body>
</html>
<!-- templates/user/list.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>ユーザー一覧</title>
</head>
<body>

<!-- fragment を挿入 -->
<div th:replace="~{fragments/layout :: header}"></div>

<main>
    <h2>ユーザー一覧</h2>
    <ul>
        <li th:each="u : ${users}" th:text="${u.name}"></li>
    </ul>
</main>

<div th:replace="~{fragments/layout :: footer}"></div>

</body>
</html>

th:replaceth:insert の違い:

属性動作
th:replace呼び出し側のタグごと置換
th:insert呼び出し側のタグ中に挿入
th:includefragment の中身のみ挿入(非推奨、3.0+ で deprecated)

JS / CSS との連携

<!-- JS にサーバー側変数を渡す -->
<script th:inline="javascript">
    /*<![CDATA[*/
    var user = {
        id:   /*[[${user.id}]]*/ 0,
        name: /*[[${user.name}]]*/ ''
    };
    var contextPath = /*[[@{/}]]*/ '/';
    /*]]>*/
</script>

<!-- CSS にも値を埋められる -->
<style th:inline="css">
    .user-bg { background-image: url(/*[[@{/img/bg.jpg}]]*/ ''); }
</style>

ユーティリティオブジェクト

<!-- 日付フォーマット -->
<span th:text="${#dates.format(user.birthday, 'yyyy/MM/dd')}">2000/01/01</span>

<!-- 数値フォーマット -->
<span th:text="${#numbers.formatDecimal(price, 1, 'COMMA', 2, 'POINT')}">1,234.56</span>

<!-- 文字列操作 -->
<span th:text="${#strings.toUpperCase(user.name)}">YAMADA</span>
<span th:text="${#strings.abbreviate(text, 30)}">短縮...</span>

<!-- リスト操作 -->
<span th:text="${#lists.size(users)}">10</span>

<!-- null safe -->
<span th:text="${user?.address?.zip ?: '未登録'}">000-0000</span>

FAQ

Q: th:textth:utext の違い
A: th:text は HTML エスケープあり(XSS 安全)、th:utext はエスケープなし(XSS 注意、自己責任)。原則 th:text

Q: Thymeleaf 2 と 3 の違い
A: 3.0 から th:include 廃止、~{...} fragment 構文導入、HTML5 への移行など。Spring Boot 2.0+ は Thymeleaf 3 が標準。

Q: 開発中に変更が反映されない
A: spring.thymeleaf.cache=falseapplication.properties に。または DevTools を入れる。本番は cache=true。