Hotwire の Turbo と Stimulus を触ってみて
CREATED: 2026 / 05 / 05 Tue
UPDATED: 2026 / 05 / 05 Tue
Hotwire とは
Hotwire は Rails 7 からデフォルトで組み込まれた、JavaScript をほとんど書かずにリッチな UI を実現するための仕組みで、以下の要素で成り立っています。
Turbo Drive → ページ遷移を高速化(自動、コード不要)
Turbo Frame → ページの一部を GET リクエストで更新
Turbo Stream → POST/PUT/PATCH/DELETE 時に複数箇所を更新
Stimulus → Turbo を利用しながら JavaScript で画面に動きをつける
と言ってもこれら自体は JavaScript でできてます🙆♂️
Turbo Drive
仕組み
Turbo Drive はページ遷移を高速化する仕組みで、特別なコードを書かなくても Turbo を導入するだけで自動的に有効になります。
これまでの Rails アプリケーションではページ全体をリクエストし、HTML・CSS・JS をすべて読み込み直していましたが、Turbo Drive を使うと以下のようになります。
# 通常のブラウザ(Drive なし)
リンククリック
→ ページ全体をリクエスト
→ HTML, CSS, JS を全部読み込み直し
→ ページ全体を再描画
# Turbo Drive あり
リンククリック
→ ページ全体をリクエスト
→ <body> だけ差し替え
→ <head> の CSS, JS は再読み込みしない ← ここが速い
CSS や JS が増えれば増えるほど Turbo Drive の恩恵が大きくなります。
初回レンダリングは通常のブラウザと変わりませんが、2回目以降のページ遷移から効果を発揮します。
ちなみに、ブラウザの JavaScript が無効な場合は通常のページ遷移にフォールバックします。
Turbo Frame
基本的な使い方
Turbo Frame はページの一部だけを更新する仕組みです。
GET リクエスト(Rails コントローラーの show や edit)で取得した HTML を対応する turbo-frame タグのコンポーネントと置き換えることができます。
turbo_frame_tag ヘルパーでフレームを定義します。
<%# ページ上のフレーム %>
<%= turbo_frame_tag "new_item" do %>
<%= render "form", item: @item %>
<% end %>
以下の HTML が生成されます。
<turbo-frame id="new_item"> ... </turbo-frame>
この HTML を返却する API を呼び出すと、そのレスポンスに含まれる id と一致する turbo-frame だけを抽出して、クライアントが部分更新を行います(他のフレームには影響しない)。
サーバー側は普通のページを返すだけで、差し替えはブラウザ側の Turbo が担当します。
dom_id で一意な ID を生成
複数のレコードを一覧表示する場合、dom_id ヘルパーでレコードごとに一意な ID を生成します。
dom_id(item) # item.id が 1 なら → "item_1"
# item.id が 2 なら → "item_2"
<%= turbo_frame_tag dom_id(item) do %>
<turbo-frame id="item_1">...</turbo-frame> <turbo-frame id="item_2">...</turbo-frame>
これによって id の照合ができるというわけです。
Turbo Stream
Turbo Frame との違い
| Turbo Frame | Turbo Stream | |
|---|---|---|
| リクエスト | GET | POST/PUT/PATCH/DELETE |
| 更新箇所 | 1フレームのみ | 複数箇所同時に可能 |
Turbo Frame は GET リクエストによって取得した HTML を部分的に置き換えのために、Turbo Stream は POST/PUT/PATCH/DELETE リクエストの完了時に発生する画面の更新(主に複数の箇所の更新)のために利用することができます。
7つのアクション
Turbo Stream には DOM を操作する7つのメソッドがあります。
| メソッド | 動作 |
|---|---|
prepend | 要素の先頭に追加 |
append | 要素の末尾に追加 |
before | 要素の前に挿入 |
after | 要素の後に挿入 |
replace | 要素を丸ごと置き換え |
update | 要素の中身だけ置き換え |
remove | 要素を削除 |
使用例
create.turbo_stream.erb には複数の画面更新処理をまとめることができます。
<%# create.turbo_stream.erb %>
<%= turbo_stream.prepend "items-list", partial: "items/item", locals: { item: @item } %>
<%= turbo_stream.replace "new_item" do %>
<%= turbo_frame_tag "new_item" do %>
<%= render "form", item: Item.new %>
<% end %>
<% end %>
turbo_stream.prepend は新しい item をリストの先頭に追加し、turbo_stream.replace はフォームを空の状態にリセットしています。
このように、Turbo Stream を使えば一括で更新できます。
respond_to での使い方
コントローラーでは respond_to ブロックを使って Turbo Stream レスポンスと通常の HTML レスポンスを出し分けることができます。
respond_to do |format|
format.turbo_stream # Accept: text/vnd.turbo-stream.html のとき選ばれる
format.html { redirect_to items_path } # Accept: text/html のとき選ばれる
end
Turbo が送るリクエストには Accept: text/vnd.turbo-stream.html ヘッダーが自動的に付きます。
これにより format.turbo_stream が選ばれ、対応するテンプレートファイル(create.turbo_stream.erb など)が自動探索されます。
# ブロックなし → create.turbo_stream.erb を自動探索
format.turbo_stream
# ブロックあり → インラインで Turbo Stream 命令を組み立てる
format.turbo_stream do
render turbo_stream: turbo_stream.replace("new_item") { ... }
end
ブラウザで JavaScript が無効になっている場合は Accept ヘッダーが自動でつきません。
そういう場合は、format.html が選ばれ、従来通りの方法でクライアントにページが返されます。
Stimulus
Stimulus を使うことで、Turbo を利用しながら JavaScript(コントローラーに定義されます) をいい感じに利用することができます。 Turbo と組み合わせてインタラクティブな UI を実現できます。
3つの基本概念
data-controller → コントローラーのスコープを定義
data-action → イベントとメソッドを紐付ける
data-target → DOM 要素を参照する
data-value → データを渡す
Controllers
data-controller が付いた要素とその子要素の中だけで JavaScript が動作します。
<span data-controller="checkbox">
← スコープ開始 <input data-[controller名]-target="input" /> ← スコープ内
</span>
← スコープ終了
<span class="item-title">...</span> ← スコープ外、関係ない
Targets
data-[controller名]-target で DOM 要素を参照します。
static targets = ['input']
data-[controller名]-target="input"
this.inputTarget // → <input> 要素そのものを参照
Values
data-[controller名]-[value名]-value でデータを渡します。
static values = { url: String }
data-[controller名]-url-value="/items/1"
this.urlValue // → "/items/1"
Actions
data-action でイベントとメソッドを紐付けます。
data-action="[イベント名]->[controller名]#[メソッド名]"
data-action="change->checkbox#toggle"
よく使うイベントは次の通りです。
| イベント | 発生タイミング |
|---|---|
click | クリックしたとき |
change | 値が変わったとき |
submit | フォームを送信したとき |
keyup | キーを離したとき |
mouseover | マウスが乗ったとき |
コントローラーの自動読み込み
*_controller.js という命名規則を守るだけで設定変更不要で自動的に読み込まれます。
app/javascript/controllers/checkbox_controller.js
→ data-controller="checkbox" として自動登録
読み込みの流れは次の通りです。
application.html.erb
└─ javascript_importmap_tags
└─ application.js
└─ import "controllers"(index.js)
└─ eagerLoadControllersFrom("controllers", application)
└─ checkbox_controller.js を自動検出・登録
を仕舞い
参考資料📕
- The Turbo Rails Tutorial | hotrails.dev
- Turbo — The speed of a single-page web application without having to write any JavaScript
- Introduction — Turbo Handbook
- Stimulus: A modest JavaScript framework for the HTML you already have
- The Origin of Stimulus — Stimulus Handbook
- Introduction — Stimulus Handbook