plan-4ging

2026/04/03

htmx とは

HTML 属性を追加するだけで、通常 JavaScript で書く必要がある Ajax、部分更新などのインタラクティブな動作を実現できる JavaScript ライブラリ
JavaScript をほとんど書かずに動的な UI を実現できる

<!-- これだけで Ajax でデータを取得して div を更新できる -->
<button hx-get="/api/users" hx-target="#result">
  ユーザーを読み込む
</button>
<div id="result"></div>

従来との違い

htmx は MPA のシンプルさを保ちながら、SPA のような動的な UI を実現する、中間の第三の選択肢的存在

【MPA】
ページ遷移のたびにサーバーから HTML 全体を取得して全画面再描画する
→ 画面がちらつく、遷移のたびに待ち時間が発生する
→ 動的な部分更新には JavaScript を自前で書く必要がある

【MPA + htmx】
ページ全体ではなく「変化した部分の HTML」だけをサーバーから受け取る
→ ちらつかない、JavaScript をほとんど書かない
→ サーバーは HTML を返す(JSON ではなく)
→ ルーティング・状態管理をサーバーサイドで完結させられる

比較(MPA, MPA+htmx, SPA)

MPAMPA + htmxSPA(React・Vue)
ページ遷移全画面再描画部分的に HTML を差し替えJavaScript で DOM を書き換え
サーバーが返すものHTMLHTML(部分)JSON
JavaScript の量少ない少ない多い
ビルドステップ不要不要必要(webpack・Vite 等)
SEO 対応△(SSR が必要)
初回ロード速い速い遅め
複雑な UI

基本構文・使い方

セットアップ

<!-- CDN から読み込む -->
<script src="https://unpkg.com/htmx.org@2.0.0"></script>

<!-- または npm でインストール -->
npm install htmx.org

主要な属性

下記一例

属性概要
hx-getGET リクエストを送信する
hx-postPOST リクエストを送信する
hx-putPUT リクエストを送信する
hx-deleteDELETE リクエストを送信する
hx-targetレスポンスを挿入する対象要素
hx-swap挿入方法(innerHTML / outerHTML等)

基本パターン

<!-- ① ボタンクリックで部分更新 -->
<button hx-get="/users/list"
        hx-target="#user-list"
        hx-swap="innerHTML">
  ユーザー一覧を読み込む
</button>
<div id="user-list"><!-- ここに結果が挿入される --></div>
<!-- ② フォーム送信 -->
<form hx-post="/users"
      hx-target="#result"
      hx-swap="outerHTML">
  <input type="text" name="name" placeholder="名前">
  <button type="submit">登録</button>
</form>
<div id="result"></div>
<!-- ③ 削除ボタン -->
<tr id="user-1">
  <td>Taro</td>
  <td>
    <button hx-delete="/users/1"
            hx-target="#user-1"
            hx-swap="outerHTML"
            hx-confirm="本当に削除しますか?">
      削除
    </button>
  </td>
</tr>

レスポンス

サーバーは完全な HTML ではなく、部分的な HTML(フラグメント) を返す

# Rails
class UsersController < ApplicationController
  def index
    @users = User.all

    respond_to do |format|
      format.html  # 通常のページ表示(index.html.erb)
      format.any { render partial: 'users/list', locals: { users: @users } }
      # htmx からのリクエストには部分 HTML を返す
    end
  end

  def create
    @user = User.new(user_params)

    if @user.save
      # 新しく追加されたユーザーの行だけを返す
      render partial: 'users/user_row', locals: { user: @user }
    else
      # エラーフォームを返す(HTTP ステータス 422)
      render partial: 'users/form', locals: { user: @user },
             status: :unprocessable_entity
    end
  end

  def destroy
    User.find(params[:id]).destroy
    # 削除後は空レスポンス(hx-swap="outerHTML" で行が消える)
    head :ok
  end
end
<%# app/views/users/_user_row.html.erb(部分テンプレート) %>
<tr id="user-<%= user.id %>">
  <td><%= user.name %></td>
  <td><%= user.email %></td>
  <td>
    <button hx-delete="/users/<%= user.id %>"
            hx-target="#user-<%= user.id %>"
            hx-swap="outerHTML"
            hx-confirm="削除しますか?">
      削除
    </button>
  </td>
</tr>

リクエストヘッダー

自動的に以下ヘッダーをリクエストに付与する
これにより、サーバー側で「htmx からのリクエストか通常のリクエストか」を判別できる

# htmx リクエストの判別
request.headers['HX-Request']       # => "true"(htmx からのリクエスト)
request.headers['HX-Target']        # => "user-list"(ターゲット要素の ID)
request.headers['HX-Trigger']       # => "search-input"(トリガー要素の ID)
request.headers['HX-Current-URL']   # => 現在のページの URL
request.headers['HX-Trigger-Name']  # => トリガー要素の name 属性

# Rails
def htmx_request?
  request.headers['HX-Request'] == 'true'
end

def index
  @users = User.search(params[:q])

  if htmx_request?
    render partial: 'users/list'  # 部分 HTML
  else
    render :index                  # フルページ
  end
end

ユースケース

向いている場面

- 複雑な UI より「動く」ことが重要
- フロントエンド専任がいないチームで開発する
- 開発速度重視
- 既存の MPA(Rails・Laravel)に動的な要素を追加したい
  - フルリライトなしで部分的にインタラクティブにできる

向いていない場面

- 複雑なクライアント状態管理が必要なアプリ(ショッピングカート・複雑なフォームなど)
- 大規模・複雑な UI コンポーネントが必要なアプリ
- リアルタイム性が高いアプリ(チャット・ゲームなど)
  - WebSocket ベースのリアルタイム通信が主役の場合
- モバイルアプリと API を共用したい場合

参考