はじめに:対象読者と課題認識
本記事は、Spring MVCとThymeleafで構築された既存システムを運用しているエンジニアを主な対象としています。特に、SPA(ReactやVue)への全面移行は難しいが、ユーザーエクスペリエンス(UX)の改善を図りたい方や、jQueryベースのコードを維持しつつ段階的に改善したい実務者に向けた内容です。
現状の課題としては以下が挙げられます。
- 画面リロードを前提とした設計から脱却できていない
- Ajax導入時に「HTML返却」と「JSON返却」のどちらを選ぶべきか判断基準が曖昧
- バリデーションや更新処理との整合性が崩れやすい
- 結果としてControllerやテンプレートが肥大化し、保守性が低下する
結論:Thymeleaf × Ajaxの設計戦略
ThymeleafでHTMLを返すAjax設計は、既存システムの段階的なモダナイゼーションにおいて有効なアプローチです。ただし、更新処理やバリデーションとの整合性をしっかり設計しないと、システム全体の破綻を招く恐れがあります。
判断基準のポイントは以下の通りです。
- 表示中心の処理はHTML返却で対応
- 状態管理や複雑な相互作用が必要な部分はJSON/API化を検討
- 最初から完全に分離を目指すのではなく、「境界を意識したHTMLとJSONの混在」が現実的かつ効果的
なぜ「HTMLを返すAjax」が再評価されているのか
従来のSpring MVC + Thymeleafベースのシステムでは、画面遷移ごとにフルリロードし、ControllerからThymeleafテンプレートを介してViewを描画する形が主流でした。一方、SPA(Single Page Application)ではAPI(JSON)を返し、フロントエンド側でレンダリングを完結させます。
しかし現場では、以下のような理由でSPAへの全面移行が困難なケースが多く見られます。
- フロントエンドの全面刷新にかかるコストが大きい
- Java側に業務ロジックが密結合しており、分離が難しい
このような状況下で注目されているのが、「サーバサイドでレンダリング済みのHTMLをAjaxで取得し、部分的に差し替える」設計パターンです。これにより、既存資産を活かしつつUXの改善が可能になります。
基本パターン:詳細エリアの差し替え
Controllerの例
@GetMapping("/user/{id}/detail")
public String detail(@PathVariable Long id, Model model) {
model.addAttribute("user", userService.findById(id));
return "user/detail :: content";
}
jQueryによるAjax呼び出し
$.get("/user/" + userId + "/detail", function(html) {
$("#detail-area").html(html);
});
Thymeleafのフラグメント例
<div th:fragment="content">
<p th:text="${user.name}"></p>
</div>
このパターンは多くの現場で一度は試される典型的な手法です。部分的にHTMLを差し替えることで、画面全体のリロードを避けつつ表示を更新できます。
設計の本質:更新処理との整合性を保つ
AjaxでHTMLを返す設計において最も重要なのは、表示処理だけでなく更新処理やバリデーションとの整合性をどう保つかです。以下の前提で設計を考えます。
- 一覧や詳細表示はAjaxでHTMLを返す
- 更新処理は同期リクエストまたはAjax(HTML返却)で混在する可能性がある
パターン別の設計整理
パターン1:更新は同期(PRGパターン)
- POST → Redirect → GETの流れを採用
- Ajaxは参照(表示)に限定
メリット:設計がシンプルでバリデーション処理も自然に行える
デメリット:UXが部分的に中途半端になることがある
パターン2:更新もAjaxでHTML返却
@PostMapping("/user/update")
public String update(UserForm form, BindingResult result, Model model) {
if (result.hasErrors()) {
return "user/edit :: form";
}
userService.update(form);
return "user/detail :: content";
}
このパターンでは、更新成功時とバリデーションエラー時で返すフラグメントを切り替えます。バリデーションエラーもHTMLで返すことで、画面の一貫性を保ちつつ部分更新が可能です。
バリデーション設計の落とし穴と対策
問題1:エラー表示の再利用が難しい
Thymeleafの標準的なエラー表示は以下のように記述します。
<div th:if="${#fields.hasErrors('name')}">
<span th:errors="*{name}"></span>
</div>
Ajaxでこのまま利用する場合、フラグメント単位で返す必要があり、フォーム全体の再描画が前提となります。
問題2:状態の分断によるUI不整合
- 部分的に更新すると、他のUIコンポーネントとの状態が不整合になる
- エラーメッセージや通知がバラバラに表示されることがある
対策:更新単位をフラグメント単位に揃え、中途半端な粒度での分割を避けることが重要です。
HTML返却とJSON返却の使い分け基準
| 観点 | HTML返却 | JSON返却 |
|---|---|---|
| 実装コスト | 低い | 高い |
| 既存資産の活用 | ◎ | △ |
| 柔軟性 | 低い | 高い |
| 複雑なUI対応 | 苦手 | 得意 |
段階的移行戦略:現実的な進め方
Step1:部分更新の導入
- 一覧や詳細画面をAjax化し、Thymeleafのフラグメントを活用して部分的に差し替え
Step2:更新処理のAjax対応
- バリデーションを含めてHTMLで返却し、UIの一貫性を確保
Step3:境界の見極め
複雑な状態管理や双方向の相互作用が必要になった段階で、JSON APIを導入しフロントエンド側でのレンダリングに移行することを検討します。この「境界」を意識した混在が現実的な設計戦略です。
アンチパターンに注意
- すべてのAPIでHTMLを返却し続けることによる肥大化
- フラグメントが乱立し、管理が困難になる
- ControllerがViewに依存しすぎて肥大化する
- JSONとHTMLの返却が混在し、コードベースがカオス化する
まとめ:この記事の価値
- レガシー環境を壊さずにUXを段階的に改善する具体的なロードマップを示します
- 「HTML返却は悪か?」という疑問に対して現実的かつ実践的な答えを提供します
- Ajax導入時に陥りやすいバリデーション設計の落とし穴を回避する方法を解説します