Hotwire の Turbo と Stimulus を触ってみて
CREATED: 2026 / 05 / 05 Tue
UPDATED: 2026 / 05 / 05 Tue
Hotwire とは
Hotwire は Rails 7 からデフォルトで組み込まれた、JavaScript をほとんど書かずにリッチな UI を実現するためのフレームワークです。 3つの技術で構成されています。
Turbo Drive → ページ遷移を高速化(自動、コード不要)
Turbo Frame → ページの一部を GET リクエストで更新
Turbo Stream → フォーム送信後に複数箇所を DOM 操作
それぞれ役割がはっきりと分かれており、操作の種類に応じて使い分けます。
Turbo Drive
仕組み
Turbo Drive はページ遷移を高速化する仕組みで、特別なコードを書かなくても Turbo を導入するだけで自動的に有効になります。
通常のブラウザはリンクをクリックするとページ全体をリクエストし、HTML・CSS・JS をすべて読み込み直します。 Turbo Drive はこれを次のように変えます。
# 通常のブラウザ(Drive なし)
リンククリック
→ ページ全体をリクエスト
→ HTML, CSS, JS を全部読み込み直し
→ ページ全体を再描画
# Turbo Drive あり
リンククリック
→ ページ全体をリクエスト
→ <body> だけ差し替え
→ <head> の CSS, JS は再読み込みしない ← ここが速い
CSS や JS が増えれば増えるほど Drive の恩恵が大きくなります。
Drive はクライアントサイドの JavaScript ライブラリです。初回レンダリングは通常のブラウザと変わりませんが、2回目以降のページ遷移から効果を発揮します。JavaScript が無効な場合は通常のページ遷移にフォールバックします。
Prefetch
Turbo Drive にはリンクにマウスをホバーしただけで裏側でページを先読みする Prefetch 機能があります。
リンクにホバー → 裏側で fetch 開始
クリック → すでに取得済みなので即座に表示
ユーザーがリンクをクリックするまでの数百ミリ秒の間にフェッチを済ませておくことで、体感速度を大幅に向上できます。
data-turbo-track: “reload”
CSS が変更されたときに Drive が自動でページをリロードする仕組みです。
<link rel="stylesheet" href="/assets/application.css" data-turbo-track="reload">
Drive は通常 <body> だけを差し替えますが、data-turbo-track="reload" を指定した要素の href が変わっていた場合はページ全体をリロードします。
デプロイ後に古い CSS が適用され続けてしまう問題を防ぐための仕組みです。
通常のページ遷移(Drive)
→ <head> を比較
→ data-turbo-track の href が変わっていたら → ページ全体をリロード
→ 変わっていなければ → <body> だけ差し替え(通常の Drive の動作)
Turbo Frame
基本的な使い方
Turbo Frame はページの一部だけを更新する仕組みです。リンククリック(GET リクエスト)に対応しています。
turbo_frame_tag ヘルパーでフレームを定義します。
<%# ページ上のフレーム %>
<%= turbo_frame_tag "new_todo" do %>
<%= render "form", todo: @todo %>
<% end %>
以下の HTML が生成されます。
<turbo-frame id="new_todo">
...
</turbo-frame>
Edit リンクをクリックすると次の流れで動作します。
Edit リンクをクリック
→ GET /todos/1/edit(HTTP リクエスト発生)
→ edit.html.erb を返す
→ レスポンスから id が一致する turbo-frame だけ抽出して差し替え
→ 他のフレームは一切触れない
サーバー側は普通のページを返すだけで、差し替えはブラウザ側の Turbo が担当します。
dom_id で一意な ID を生成
複数のレコードを一覧表示する場合、dom_id ヘルパーでレコードごとに一意な ID を生成します。
dom_id(todo) # todo.id が 1 なら → "todo_1"
# todo.id が 2 なら → "todo_2"
<%= turbo_frame_tag dom_id(todo) do %>
<turbo-frame id="todo_1">...</turbo-frame>
<turbo-frame id="todo_2">...</turbo-frame>
todo ごとに独立したフレームになるため、複数の todo が並んでいても正しい行だけを更新できます。
テンプレートはパーシャルではなく通常の erb
edit.html.erb はパーシャル(_edit.html.erb)ではなく通常のビューファイルです。
Turbo Frame はブラウザから GET /todos/1/edit という HTTP リクエストを送り、そのレスポンスから対応する turbo-frame を抽出します。
パーシャルにしてしまうと URL でアクセスできなくなるため、通常のビューとして定義する必要があります。
Turbo Stream
Turbo Frame との違い
| Turbo Frame | Turbo Stream | |
|---|---|---|
| リクエスト | GET(リンククリック) | POST/PATCH/DELETE(フォーム送信) |
| 更新箇所 | 1フレームのみ | 複数箇所同時に可能 |
Turbo Frame はリンクのクリック、Turbo Stream はフォームの送信後に使うのが基本的な使い分けです。
7つのアクション
Turbo Stream には DOM を操作する7つのメソッドがあります。
| メソッド | 動作 |
|---|---|
prepend | 要素の先頭に追加 |
append | 要素の末尾に追加 |
before | 要素の前に挿入 |
after | 要素の後に挿入 |
replace | 要素を丸ごと置き換え |
update | 要素の中身だけ置き換え |
remove | 要素を削除 |
使用例
create.turbo_stream.erb では複数の DOM 操作を1レスポンスでまとめて記述できます。
<%# create.turbo_stream.erb %>
<%= turbo_stream.prepend "todos-list", partial: "todos/todo", locals: { todo: @todo } %>
<%= turbo_stream.replace "new_todo" do %>
<%= turbo_frame_tag "new_todo" do %>
<%= render "form", todo: Todo.new %>
<% end %>
<% end %>
1つ目は新しい todo をリストの先頭に追加し、2つ目はフォームを空の状態にリセットしています。 このように1回のレスポンスで複数箇所を同時に更新できるのが 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 todos_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_todo") { ... }
end
Stimulus
Stimulus は HTML に data-* 属性を書くことで JavaScript の動作を紐付けるフレームワークです。
Turbo と組み合わせることで、サーバー側のレンダリングを維持しながらインタラクティブな UI を実現できます。
3つの基本概念
data-controller → コントローラーのスコープを定義
data-action → イベントとメソッドを紐付ける
data-target → DOM 要素を参照する
data-value → データを渡す
Controllers
data-controller が付いた要素とその子要素の中だけでコントローラーが動作します。
<span data-controller="checkbox"> ← スコープ開始
<input data-checkbox-target="input" /> ← スコープ内
</span> ← スコープ終了
<span class="todo-title">...</span> ← スコープ外、関係ない
同じコントローラーが複数あっても、それぞれ独立したインスタンスとして動作します。
Targets
data-[controller名]-target で DOM 要素を参照します。
static targets = ['input']
data-checkbox-target="input"
this.inputTarget // → <input> 要素そのものを参照
Values
data-[controller名]-[value名]-value でデータを渡します。
static values = { url: String }
data-checkbox-url-value="/todos/1"
this.urlValue // → "/todos/1"
Actions
data-action でイベントとメソッドを紐付けます。
data-action="[イベント名]->[コントローラー名]#[メソッド名]"
data-action="change->checkbox#toggle"
change → チェックボックスの状態が変わったとき
checkbox → checkbox_controller.js
toggle → toggle() メソッドを呼ぶ
よく使うイベントは次の通りです。
| イベント | 発生タイミング |
|---|---|
click | クリックしたとき |
change | 値が変わったとき |
submit | フォームを送信したとき |
keyup | キーを離したとき |
mouseover | マウスが乗ったとき |
要素の種類によってデフォルトのイベントが決まっているので省略できます。
data-action="change->checkbox#toggle"
data-action="checkbox#toggle" ← 同じ意味(checkbox のデフォルトは change)
コントローラーの自動読み込み
*_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 を自動検出・登録
使い分けまとめ
CRUD 操作ごとにどの技術を使うかを整理するとこうなります。
| 操作 | 使用技術 | 理由 |
|---|---|---|
| Edit リンククリック | Turbo Frame | GET リクエストのため |
| Cancel リンククリック | Turbo Frame | GET リクエストのため |
| Create 成功 | Turbo Stream | リスト追加+フォームリセットの2箇所を同時更新 |
| Update 成功 | Turbo Stream | フォーム送信のレスポンスのため |
| Delete | Turbo Stream | フォーム送信のレスポンスのため |
| チェックボックス切り替え | Stimulus + Turbo Stream | ボタンなしで即時反映するため |
Turbo Frame は GET、Turbo Stream は POST/PATCH/DELETE と覚えておくと判断しやすいです。 Stimulus はボタンを押さずにイベントを拾いたい場合や、JavaScript で動的な振る舞いを加えたい場合に出番があります。
を仕舞い
参考資料📕
- Turbo Rails Tutorial Introduction | hotrails.dev
- CRUD controller with Ruby on Rails | hotrails.dev
- CSS and Ruby on Rails | hotrails.dev
- Turbo Drive | hotrails.dev
- Turbo Frames and Turbo Streams | hotrails.dev
- Turbo Streams | hotrails.dev
- Turbo Streams security | hotrails.dev
- Flash messages with Hotwire | hotrails.dev
- Empty states | hotrails.dev
- Turbo Rails CRUD | hotrails.dev
- Nested Turbo Frames | hotrails.dev
- Quote totals with Turbo Frames | 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