タイトル: 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="'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></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('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></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>© 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}]]*/ ''
};
var contextPath = /*[[@{/}]]*/ '/';
/*]]>*/
</script>
<!-- CSS にも値を埋められる -->
<style th:inline="css">
.user-bg { background-image: url(/*[[@{/img/bg.jpg}]]*/ ''); }
</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, '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></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>