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

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

この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li><strong>Thymeleaf</strong> は Spring Boot 標準のサーバーサイドテンプレートエンジン</li><li><strong>HTML として開ける</strong>のが最大の特徴。デザイナーがそのまま編集可能</li><li>基本属性: <code>th:text</code> / <code>th:if</code> / <code>th:each</code> / <code>th:href</code></li><li>レイアウト: <code>th:fragment</code> + <code>th:replace</code> / <code>th:insert</code></li><li>Spring MVC との連携: <code>model.addAttribute("user", user)</code> → <code>${user.name}</code></li></ul></td></tr></table>
<h2>Thymeleaf とは</h2>

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

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>項目</th><th>Thymeleaf</th><th>JSP</th></tr></thead>
<tbody>
<tr><td>HTML として開ける</td><td>◯</td><td>×</td></tr>
<tr><td>Spring Boot 標準</td><td>◯</td><td>△ (要設定)</td></tr>
<tr><td>記法</td><td>HTML 属性 <code>th:*</code></td><td>カスタムタグ <code><c:if></code></td></tr>
<tr><td>レイアウト</td><td>fragment / decorator</td><td>include / sitemesh</td></tr>
<tr><td>学習コスト</td><td>低(HTML が分かれば)</td><td>中</td></tr>
</tbody>
</table>

<h2>セットアップ (Spring Boot)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-xml"><!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency></code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-text">src/main/resources/
├── templates/        ← Thymeleaf テンプレート (デフォルト)
│   ├── index.html
│   └── user/
│       ├── list.html
│       └── detail.html
└── static/           ← CSS / JS / 画像
    ├── css/app.css
    └── js/app.js</code></pre>
<h2>基本記法</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!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="&apos;ADMIN&apos;">管理者</p>
    <p th:case="&apos;USER&apos;">ユーザー</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}? &apos;odd&apos; : &apos;even&apos;">
    <td th:text="${stat.count}">1</td>
    <td th:text="${u.name}">名前</td>
</tr>

</body>
</html></code></pre>
<h2>フォーム</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><form th:action="@{/user/save}" th:object="${form}" method="post">

    <!-- フィールドバインド -->
    <input type="text" th:field="*{name}" />
    <span th:if="${#fields.hasErrors(&apos;name&apos;)}"
          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></code></pre>
<h2>Spring MVC との連携</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-java">@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";
    }
}</code></pre>
<h2>レイアウト (fragment)</h2>

<p>共通ヘッダ・フッタを切り出して再利用:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- 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></code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-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></code></pre>
<p><code>th:replace</code> と <code>th:insert</code> の違い:</p>

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>属性</th><th>動作</th></tr></thead>
<tbody>
<tr><td><code>th:replace</code></td><td>呼び出し側のタグごと置換</td></tr>
<tr><td><code>th:insert</code></td><td>呼び出し側のタグ中に挿入</td></tr>
<tr><td><code>th:include</code></td><td>fragment の中身のみ挿入(非推奨、3.0+ で deprecated)</td></tr>
</tbody>
</table>

<h2>JS / CSS との連携</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- JS にサーバー側変数を渡す -->
<script th:inline="javascript">
    /*<![CDATA[*/
    var user = {
        id:   /*[[${user.id}]]*/ 0,
        name: /*[[${user.name}]]*/ &apos;&apos;
    };
    var contextPath = /*[[@{/}]]*/ &apos;/&apos;;
    /*]]>*/
</script>

<!-- CSS にも値を埋められる -->
<style th:inline="css">
    .user-bg { background-image: url(/*[[@{/img/bg.jpg}]]*/ &apos;&apos;); }
</style></code></pre>
<h2>ユーティリティオブジェクト</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- 日付フォーマット -->
<span th:text="${#dates.format(user.birthday, &apos;yyyy/MM/dd&apos;)}">2000/01/01</span>

<!-- 数値フォーマット -->
<span th:text="${#numbers.formatDecimal(price, 1, &apos;COMMA&apos;, 2, &apos;POINT&apos;)}">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 ?: &apos;未登録&apos;}">000-0000</span></code></pre>
<h2>FAQ</h2>

<p><strong>Q: <code>th:text</code> と <code>th:utext</code> の違い</strong><br />
A: <code>th:text</code> は HTML エスケープあり(XSS 安全)、<code>th:utext</code> はエスケープなし(XSS 注意、自己責任)。原則 <code>th:text</code>。</p>

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

<p><strong>Q: 開発中に変更が反映されない</strong><br />
A: <code>spring.thymeleaf.cache=false</code> を <code>application.properties</code> に。または DevTools を入れる。本番は cache=true。</p>