htmxは、HTML属性を追加するだけでAJAXリクエスト、WebSocket接続、サーバーサイドイベントなどを実現できる軽量ライブラリです。ReactやVueのような本格的なJavaScriptフレームワークを導入せずに、リッチなインタラクションを持つWebアプリケーションを構築できます。
近年、フロントエンドの複雑化に疲弊した開発者の間で「シンプルに戻ろう」という動きが広がっています。htmxはまさにその流れを象徴する技術であり、サーバーサイドの開発者が自然にモダンなUIを構築できる手段を提供します。筆者も社内ツールの開発でhtmxを採用し、その開発速度に驚かされました。
htmxの導入は非常にシンプルです。CDNからスクリプトを読み込むだけで利用可能になります。
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
基本的なhtmx属性を使った例を見てみましょう。
<!-- ボタンクリックでサーバーからHTMLを取得し、指定要素に挿入 -->
<button hx-get="/api/users" hx-target="#user-list" hx-swap="innerHTML">
ユーザー一覧を読み込む
</button>
<div id="user-list">
<!-- ここにサーバーから返されたHTMLが挿入される -->
</div>
サーバー側は完全なHTMLページではなく、HTMLの断片(フラグメント)を返すだけです。これがhtmxの核心的な考え方であり、サーバーはJSONではなくHTMLを返すというパラダイムです。
<form hx-post="/api/tasks" hx-target="#task-list" hx-swap="beforeend" hx-on::after-request="this.reset()">
<input type="text" name="title" placeholder="タスク名" required>
<select name="priority">
<option value="high">高</option>
<option value="medium">中</option>
<option value="low">低</option>
</select>
<button type="submit">追加</button>
</form>
<ul id="task-list">
<!-- 既存のタスクがここに表示される -->
</ul>
hx-swap="beforeend"を指定することで、サーバーから返されたHTMLがリストの末尾に追加されます。ページ全体のリロードは発生しません。
htmxの大きな利点は、サーバー側のフレームワークを問わない点です。Flask、Django、Express、Railsなど、どのフレームワークでも利用可能です。ここではFlaskを例に示します。
from flask import Flask, request, render_template_string
app = Flask(__name__)
tasks = []
@app.route('/api/tasks', methods=['POST'])
def add_task():
title = request.form.get('title')
priority = request.form.get('priority')
task = {'title': title, 'priority': priority, 'id': len(tasks) + 1}
tasks.append(task)
# HTMLフラグメントを返す(JSONではない)
return render_template_string('''
<li id="task-{{ task.id }}">
<span>{{ task.title }}</span>
<span class="badge">{{ task.priority }}</span>
<button hx-delete="/api/tasks/{{ task.id }}"
hx-target="#task-{{ task.id }}"
hx-swap="outerHTML">削除</button>
</li>
''', task=task)
@app.route('/api/tasks/<int:task_id>', methods=['DELETE'])
def delete_task(task_id):
tasks[:] = [t for t in tasks if t['id'] != task_id]
return '' # 空のレスポンスで要素が削除される
注目すべき点は、削除処理でサーバーが空文字を返している部分です。hx-swap="outerHTML"と組み合わせることで、対象要素がDOMから完全に除去されます。
htmxが特に威力を発揮するのが、リアルタイム検索です。キー入力のたびにサーバーにリクエストを送り、結果をその場で更新します。
<input type="search" name="q"
hx-get="/api/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#search-results"
hx-indicator="#loading"
placeholder="検索キーワードを入力">
<span id="loading" class="htmx-indicator">検索中...</span>
<div id="search-results">
<!-- 検索結果がここに表示される -->
</div>
hx-trigger="keyup changed delay:300ms"は、キー入力後300ミリ秒のデバウンスを設定しています。値が変化した場合のみリクエストが発行されるため、不要なサーバー負荷を抑えられます。hx-indicator属性でローディング表示も簡単に実装できます。
htmxは万能ではありません。プロジェクトの特性に応じて適切に判断する必要があります。
htmxの最大の価値は、Web開発の複雑さを劇的に削減できる点にあります。ビルドパイプラインの設定、状態管理ライブラリの選定、バンドルサイズの最適化といった、フロントエンド特有の悩みから解放されます。もちろんReactやVueが最適な場面は多々ありますが、全てのプロジェクトでSPAが必要かというと、実はそうでもないのです。htmxは「適切な道具を適切な場面で使う」という原則を思い出させてくれる、優れたライブラリです。