Thymeleaf th:hrefでリンクを動的に作る方法を徹底解説!初心者向けガイド
新人
「先輩、Spring Bootで画面を作っているときにリンクを動的に作りたいんですが、どうやって書けばいいんですか?」
先輩
「Spring MVCで画面を作るときは、Thymeleafを使うと便利だよ。特にth:hrefを使えば、動的にリンクを作ることができるんだ。」
新人
「th:hrefって普通のhrefとどう違うんですか?」
先輩
「それは良い疑問だね。じゃあ、基本から順番に説明していこう!」
1. Thymeleafとは?(基本的な役割)
Thymeleaf(タイムリーフ)は、Spring FrameworkやSpring Bootでよく使われるテンプレートエンジンです。テンプレートエンジンとは、HTMLファイルにサーバーサイドの値を埋め込んで動的にページを生成する仕組みのことです。例えば、ログインしたユーザー名や商品一覧などを画面に表示するときに便利です。ThymeleafはHTMLと自然に統合できるため、デザイナーと開発者が同じファイルを扱えるという特徴があります。
特にリンクを作る場面では、th:hrefという属性を使うことで、通常の<a href="">タグにサーバーから渡された値を組み込むことができます。これによって、動的なパラメータをURLに付けてページを遷移させることが可能になります。
2. th:href属性の基本的な書き方
th:hrefの基本的な書き方はとてもシンプルです。通常のHTMLでは次のように静的なリンクを作ります。
<a href="/home">ホーム画面へ</a>
これをThymeleafで動的にしたい場合は、th:hrefを使います。
<a th:href="@{/home}">ホーム画面へ</a>
この書き方をすると、アプリケーションのコンテキストパスを考慮した正しいURLが自動的に生成されます。例えば、プロジェクトのURLがhttp://localhost:8080の場合、実際にはhttp://localhost:8080/homeに変換されます。こうすることで、環境が変わってもURLの修正を手作業で行う必要がなくなります。
3. 静的リンクと動的リンクの違い
静的リンクとは、あらかじめ決まったURLを直接HTMLに書いておく方法です。例えば、次のように書くと常に同じリンク先になります。
<a href="/about">このサイトについて</a>
一方で動的リンクとは、サーバーから渡される値やアプリケーションの設定に応じて変化するリンクです。Thymeleafのth:hrefを使えば、動的に値を埋め込んでリンクを生成できます。
<a th:href="@{/user/{id}(id=${user.id})}">ユーザー詳細へ</a>
この例では、コントローラから渡されたuser.idの値がリンクに埋め込まれます。例えば、ユーザーIDが「5」であれば、最終的に生成されるリンクは/user/5になります。こうすることで、ユーザーごとに異なるページへのリンクを自動で作成できるわけです。
静的リンクはシンプルですが柔軟性に欠けます。対して動的リンクは、ユーザー情報やデータベースの値に応じて柔軟にリンクを作れるため、現代的なWebアプリケーションでは欠かせない仕組みです。
4. コントローラでデータを渡してリンクに反映する方法
新人
「先輩、Thymeleafのth:hrefを使うと動的なリンクが作れるのは分かりました。でも実際にデータをコントローラから渡すにはどうすればいいんですか?」
先輩
「いい質問だね。Spring MVCでは@Controllerを使って値を画面に渡せるんだ。例えばユーザーIDや商品IDを渡して、その値をth:hrefに埋め込むことでリンクを動的に生成できるよ。」
Spring MVCでリンクを動的に作るためには、まずコントローラで値を用意してビューに渡す必要があります。開発環境はpleiades+Gradleで進める前提ですので、ここでも同じ構成を想定してコード例を紹介します。以下のように@Controllerを用いたクラスを作り、Modelオブジェクトにデータを追加します。
@Controller
public class UserController {
@GetMapping("/users")
public String list(Model model) {
User user = new User(1, "Taro");
model.addAttribute("user", user);
return "user-list";
}
}
このコードでは、ユーザーオブジェクトを作成し、model.addAttributeでビューに渡しています。Thymeleaf側のHTMLでは、このuserオブジェクトを使って動的なリンクを生成できます。
<a th:href="@{/user/{id}(id=${user.id})}">ユーザー詳細ページへ</a>
こうすることで、コントローラから受け取ったuser.idをリンクに反映させ、正しい動的リンクを自動で生成できます。これがSpring MVCとThymeleafを組み合わせたth:hrefの基本的な使い方です。
5. パラメータ付きリンクの作り方
新人
「なるほど。IDを使って動的にリンクが作れるんですね。でも例えば検索条件を渡すようなパラメータ付きのリンクを作りたいときはどうすればいいんですか?」
先輩
「その場合はクエリパラメータをURLに付けてあげればいいんだ。th:hrefを使えばパラメータを動的に埋め込むのも簡単にできるよ。」
Thymeleafではth:hrefを用いてクエリパラメータを簡単に付与できます。例えば商品IDとカテゴリをクエリとして渡すリンクは次のように書けます。
<a th:href="@{/search(productId=${product.id}, category=${product.category})}">
商品検索ページへ
</a>
この書き方では、コントローラから渡されたproduct.idとproduct.categoryの値をURLのクエリパラメータとして埋め込みます。例えばIDが100でカテゴリが「book」の場合、生成されるリンクは以下のようになります。
/search?productId=100&category=book
Spring MVCのコントローラでは次のようにパラメータを受け取ることができます。
@Controller
public class SearchController {
@GetMapping("/search")
public String search(@RequestParam("productId") int productId,
@RequestParam("category") String category,
Model model) {
model.addAttribute("productId", productId);
model.addAttribute("category", category);
return "search-result";
}
}
このように、Thymeleafのth:hrefを使えばパラメータ付きの動的リンクを簡単に作成でき、Spring MVCの@RequestParamを通じて受け取ることが可能になります。これにより検索条件やフィルタリング機能を持ったページ遷移を柔軟に実現できます。
6. th:hrefと通常のaタグhrefの違いと注意点
新人
「先輩、普通のhrefとth:hrefの違いはもう少し詳しく知りたいです。実際の開発で注意することはありますか?」
先輩
「いいね。確かに両者には大事な違いがあるから整理して理解しておこう。」
通常のHTMLで使うhrefは、単純に決まった文字列のリンクを作るためのものです。静的ページを作るだけなら問題ありませんが、Spring MVCアプリケーションのようにコンテキストパスが異なる環境で動かす場合や、動的なデータを埋め込む必要がある場合には不便です。
これに対してThymeleafのth:hrefは、Spring MVCと統合されているため、以下のメリットがあります。
- アプリケーションのコンテキストパスを自動的に解決する
- 変数や式を利用して動的にリンクを生成できる
- クエリパラメータやパス変数を簡単に埋め込める
注意点としては、th:hrefを使う場合、Thymeleafの式言語(${...})を正しく理解しておく必要があることです。また、コントローラから適切にモデルを渡していないとリンク生成時にエラーになる可能性もあります。そのため、データの受け渡しとビューでの参照が正しく対応しているかを常に確認することが大切です。
例えば以下のようなケースでは注意が必要です。
<!-- モデルにuserが存在しない場合、この記述はエラーになる -->
<a th:href="@{/user/{id}(id=${user.id})}">ユーザー詳細へ</a>
このように通常のhrefとth:hrefの違いを理解し、適切に使い分けることが初心者にとっても重要なポイントです。特にSpring MVCとThymeleafを組み合わせる開発では、th:hrefを正しく使うことでリンクを動的に作成でき、メンテナンス性や拡張性を高められるのです。
7. th:hrefを使った実際のリンク動的生成例(一覧画面→詳細画面)
新人
「一覧画面から詳細画面へ移動する典型的な遷移を、Thymeleafのth:hrefでどう作ればよいですか。Spring MVCと連携した実例を見ておきたいです。」
先輩
「では、ユーザー一覧からユーザー詳細へ進む構成を段階的に作ろう。コントローラでモデルを用意して、テンプレートでth:eachとth:hrefを組み合わせれば、自然にリンクを動的に作れるよ。」
まずはSpring MVCのコントローラで一覧用のデータを準備します。開発環境はpleiadesを使用し、ビルドはGradleで行う前提です。アノテーションは@Controllerを使用し、モデルへ値を詰めてThymeleafテンプレートに渡します。ここでは簡単のためにメモリ上のリストを返す例にします。
@Controller
public class UserController {
@GetMapping("/users")
public String list(Model model) {
List<User> users = List.of(
new User(1, "Taro"),
new User(2, "Hanako"),
new User(3, "Jiro")
);
model.addAttribute("users", users);
return "user-list";
}
@GetMapping("/users/{id}")
public String detail(@PathVariable("id") int id, Model model) {
User user = new User(id, "SampleName");
model.addAttribute("user", user);
return "user-detail";
}
}
続いて一覧テンプレートです。Thymeleafのth:eachで行を繰り返し、各行のリンク先をth:hrefで動的に作ります。パス変数は@{/users/{id}(id=${u.id})}のように指定します。
<table class="table">
<thead>
<tr><th>ID</th><th>名前</th><th>操作</th></tr>
</thead>
<tbody>
<tr th:each="u : ${users}">
<td th:text="${u.id}">1</td>
<td th:text="${u.name}">Taro</td>
<td>
<a th:href="@{/users/{id}(id=${u.id})}">詳細へ</a>
</td>
</tr>
</tbody>
</table>
最後に詳細テンプレートです。コントローラから渡されたモデルを参照して画面に表示します。ここでは見やすさを重視して必要最小限の要素にとどめます。
<div>
<h3>ユーザー詳細</h3>
<p>ID: <span th:text="${user.id}">1</span></p>
<p>名前: <span th:text="${user.name}">Taro</span></p>
<p><a th:href="@{/users}">一覧に戻る</a></p>
</div>
この遷移では、一覧側でリンクを動的に作り、詳細側でモデルの中身を表示します。ThymeleafとSpring MVCが連携することで、ルーティングの変更やコンテキストパスの差異があっても、th:hrefが適切なURLを生成し、環境ごとのずれを避けられます。画面数が増えた場合でも、ルールに沿ってth:hrefを使い回すだけで保守が容易になります。リンクを動的に作るという考え方は、検索結果一覧、ページング、カテゴリ別一覧、タグ一覧などへそのまま応用できます。
8. よくあるエラーと対処法(NullPointer、パス指定の間違いなど)
新人
「実装してみると時々リンクがうまく生成されないことがあります。NullPointerのような例外や、リンク先が存在しないという問題に遭遇しました。どこを点検すればよいでしょうか。」
先輩
「典型的にはモデル未設定、属性名の不一致、パス変数の記述漏れ、マッピングのパス違いが原因だ。症状ごとに切り分けて確認していこう。」
モデル未設定や属性名不一致による参照失敗
テンプレート側で${user.id}を参照しているのに、コントローラでmodel.addAttribute("user", ...)を設定していない場合、評価時に参照が空になり、リンク生成や表示で失敗します。属性名が"users"と"user"で混同しているときも同様です。まずはコントローラとテンプレートのキーが一致しているかを確かめます。
<!-- userが未設定だと失敗する -->
<a th:href="@{/users/{id}(id=${user.id})}">詳細へ</a>
パス変数の埋め込み忘れや名前不一致
マッピングが/users/{id}であるにもかかわらず、@{/users/{id}}のidに値を渡していないとURL展開に失敗します。名前は必ず一致させ、テンプレート側で(id=${u.id})のように渡す必要があります。
<!-- NG: idを渡していない -->
<a th:href="@{/users/{id}}">詳細へ</a>
<!-- OK: idを明示的に埋め込む -->
<a th:href="@{/users/{id}(id=${u.id})}">詳細へ</a>
マッピングのパス間違いやHTTPメソッド不一致
コントローラ側の@GetMappingとテンプレートで期待するパスがずれていると、リンク先が見つからずエラーになります。URLの前後のスラッシュ、複数形と単数形、ベースパスの重複などを点検します。また、GETで表示すべきページにPOST専用のエンドポイントを書いていないかも確認します。
@Controller
public class ProductController {
// 期待する一覧のパス
@GetMapping("/products")
public String list(Model model) {
// 省略
return "product-list";
}
// 詳細のパス
@GetMapping("/products/{id}")
public String detail(@PathVariable int id, Model model) {
// 省略
return "product-detail";
}
}
コンテキストパスの誤解
ローカル環境や本番環境でアプリケーションのコンテキストパスが異なる場合、絶対パスをベタ書きすると不具合の原因になります。Thymeleafの@{...}構文を使えばコンテキストパスを気にせずに済みます。href="/users"ではなくth:href="@{/users}"と書く習慣をつけましょう。
URLエンコードやクエリの表記ミス
クエリパラメータを複数付ける際に区切りやキー名を誤ると期待通りに受け取れません。Thymeleafの@{/path(key1=${v1}, key2=${v2})}の形で記述すると安全です。
デバッグの勘所
画面で右クリックからページのソースを表示し、実際に生成されたアンカータグのhref値を確認します。リンクが意図通りの完全なURLになっているか、パラメータ名や値が期待通りかを目視で確かめると原因の切り分けが速くなります。あわせてコントローラのログにIDやカテゴリなど渡す値を出力しておくと、テンプレート側と突き合わせやすくなります。
9. 初心者がth:hrefを学ぶ際のおすすめ学習ステップ
新人
「基本は理解できました。実務に向けて学習を進めるにはどんな順番で練習するのが良いでしょうか。Thymeleafとth:hrefを効率よく身につけたいです。」
先輩
「小さな成功体験を積み上げるのが近道だよ。リンクを動的に作るという一点に焦点を当てて、段階ごとに確実にマスターしていこう。」
ステップ一 静的リンクから動的リンクへの置き換え練習
まずは既存の静的リンクをth:hrefへ置き換えます。@{/path}や@{/path/sub}の形式に慣れ、コンテキストパスが自動で補われる感覚をつかみます。ビルドや実行はpleiadesからGradleタスクを呼び出して行い、ブラウザで変化を確認します。
ステップ二 パス変数の埋め込みに挑戦
一覧から詳細へのリンクを作り、@{/items/{id}(id=${item.id})}を繰り返しで生成します。th:eachと組み合わせたテンプレートを作ることで、実践的な動的リンク生成の流れが身につきます。ここで属性名の一致や@PathVariableの連動を丁寧に点検します。
ステップ三 クエリパラメータの組み立て
検索条件やページングに使うクエリを@{/search(keyword=${k}, page=${p})}のように組み立てます。キー名の統一、値のエンコード、複数パラメータの区切りなど、実務で詰まりやすい細部を体験的に覚えます。
ステップ四 例外とエラーの早期発見
意図的に属性名をずらしたり、モデルを渡さなかったりして失敗パターンを再現し、テンプレートの評価時に何が起きるかを観察します。ソース表示で生成後のHTMLを確認し、リンクのhrefがどう変化するかを目視で追跡できるようにします。
ステップ五 共通化と再利用
ヘッダーやサイドバーに配置する共通メニューをフラグメント化し、th:replaceやth:insertで取り込みます。リンクを動的に作る処理を共通テンプレートへ集約すると、画面追加時の修正点が減り、保守性が向上します。
練習用の簡易サンプル(確認用)
以下は練習用の最小構成例です。th:hrefでリンクを動的に作る要点だけを確認できます。
@Controller
public class DemoController {
@GetMapping("/demo")
public String demo(Model model) {
model.addAttribute("id", 10);
model.addAttribute("kw", "sample");
return "demo";
}
@GetMapping("/demo/{id}")
public String show(@PathVariable int id, @RequestParam(required = false) String kw, Model model) {
model.addAttribute("id", id);
model.addAttribute("kw", kw);
return "demo-detail";
}
}
<!-- demo.html -->
<p>
<a th:href="@{/demo/{id}(id=${id})}">パス変数で詳細へ</a>
</p>
<p>
<a th:href="@{/demo/{id}(id=${id}, kw=${kw})}">パス+クエリで詳細へ</a>
</p>
<!-- demo-detail.html -->
<h3>デモ詳細</h3>
<p>ID: <span th:text="${id}">10</span></p>
<p>キーワード: <span th:text="${kw}">sample</span></p>
<p><a th:href="@{/demo}">戻る</a></p>
このように小さな練習を積み重ねると、Thymeleafでリンクを動的に作る操作が自然な手つきになります。Spring MVCと組み合わせたアプリケーション開発では、th:hrefの書き方を正しく覚えるだけで、画面遷移の組み立て、URL設計、保守のしやすさが大きく改善されます。とくにpleiades環境でGradleビルドを実行しながら、変更→表示→確認の反復を高速に回すことが定着への近道です。テンプレート、コントローラ、ルーティングの三点を同時に意識し、属性名やパスの一致を習慣化すれば、実務でも迷いなく適切なリンクを動的に作れるようになります。