# Ruby on Rails 7: Hotwire dan Turbo untuk Aplikasi Reaktif
> Panduan lengkap Hotwire dan Turbo di Rails 7. Membangun aplikasi reaktif tanpa JavaScript dengan Turbo Drive, Frames dan Streams.
- Published: 2026-01-11
- Updated: 2026-04-07
- Author: SharpSkill
- Tags: ruby on rails, hotwire, turbo, turbo frames, turbo streams
- Reading time: 12 min
---
Rails 7 merevolusi pengembangan web dengan mengintegrasikan Hotwire secara default. Stack ini memungkinkan pembangunan aplikasi yang sangat reaktif tanpa menulis satu baris pun kode JavaScript kustom. Turbo Drive, Turbo Frames, dan Turbo Streams menggantikan pendekatan SPA tradisional dengan filosofi "HTML melalui kabel".
> **Mengapa Hotwire?**
>
> Hotwire secara drastis mengurangi kompleksitas frontend. Tidak perlu React atau Vue untuk antarmuka dinamis: server mengirimkan HTML siap pakai, dan Turbo menangani pembaruan DOM secara otomatis.
## Memahami Arsitektur Hotwire
Hotwire terdiri dari tiga teknologi komplementer yang bekerja sama untuk memberikan pengalaman pengguna yang mulus tanpa kompleksitas tipikal framework JavaScript.
**Turbo Drive** mempercepat navigasi dengan mengintersep klik tautan dan pengiriman formulir. Alih-alih memuat ulang seluruh halaman, hanya konten `
` yang diganti, menjaga konteks JavaScript dan CSS.
**Turbo Frames** memecah halaman menjadi bagian-bagian independen. Setiap frame dapat diperbarui secara terpisah, memungkinkan interaksi tertarget tanpa mempengaruhi bagian halaman lainnya.
**Turbo Streams** memungkinkan pembaruan real-time melalui WebSocket atau sebagai respons terhadap permintaan HTTP. Delapan aksi CRUD tersedia untuk manipulasi DOM deklaratif.
```ruby
# Gemfile
# Installing Turbo Rails (included by default in Rails 7+)
gem 'turbo-rails'
```
Untuk proyek yang sudah ada, instalasi hanya membutuhkan satu perintah.
```bash
# installation.sh
# Installing Turbo in an existing Rails project
rails turbo:install
# Verify the JavaScript import is present
cat app/javascript/application.js
# Should contain: import "@hotwired/turbo-rails"
```
## Konfigurasi Awal Turbo Drive
Turbo Drive diaktifkan secara default di Rails 7. Semua navigasi secara otomatis menjadi "turbo" tanpa konfigurasi tambahan. Perilaku dapat disesuaikan melalui atribut data.
```erb
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
<%= yield %>
```
Turbo Drive dapat dinonaktifkan pada tautan atau formulir tertentu jika diperlukan.
```erb
<%= link_to "Dashboard", dashboard_path %>
<%= link_to "Download PDF", export_path, data: { turbo: false } %>
<%= form_with model: @document, data: { turbo: false } do |f| %>
<%= f.file_field :attachment %>
<%= f.submit "Upload" %>
<% end %>
<%= link_to "Delete", item_path(@item),
data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %>
```
> **Cache Turbo Drive**
>
> Turbo Drive meng-cache halaman yang dikunjungi. Atribut `data-turbo-track="reload"` pada aset memaksa pemuatan ulang penuh jika file CSS/JS berubah.
## Turbo Frames untuk Pembaruan Tertarget
Turbo Frames mendefinisikan zona halaman yang diperbarui secara independen. Setiap frame memiliki identifier unik dan hanya merespons respons yang mengandung frame yang cocok.
```erb
Messages
<%= render @messages %>
<%= link_to "Load more", messages_path(page: @next_page) %>
<%= render "form", message: Message.new %>
```
Respons server harus mengandung frame dengan identifier yang sama agar pembaruan berfungsi.
```erb
```
### Lazy Loading dengan Turbo Frames
Frame dapat memuat kontennya secara asinkron menggunakan atribut `src`. Konten awal ditampilkan selama pemuatan.
```erb
Dashboard
Loading statistics...
Loading notifications...
<%= render "activity/placeholder" %>
```
Controller merespons dengan view yang hanya berisi frame yang diminta.
```ruby
# app/controllers/dashboard_controller.rb
class DashboardController < ApplicationController
def stats
@user_count = User.count
@message_count = Message.count
@active_today = User.where("last_seen_at > ?", 24.hours.ago).count
# Partial rendering for the frame
render partial: "dashboard/stats"
end
end
```
```erb
<%= @user_count %>
Users
<%= @message_count %>
Messages
<%= @active_today %>
Active today
```
## Turbo Streams untuk Pembaruan Real-Time
Turbo Streams menawarkan delapan aksi untuk manipulasi DOM: `append`, `prepend`, `replace`, `update`, `remove`, `before`, `after`, dan `morph`. Aksi-aksi ini dapat dipicu melalui respons HTTP atau WebSocket.
```ruby
# app/controllers/messages_controller.rb
class MessagesController < ApplicationController
def create
@message = current_user.messages.build(message_params)
respond_to do |format|
if @message.save
# Turbo Stream response to add the message
format.turbo_stream do
render turbo_stream: [
turbo_stream.prepend("messages", @message),
turbo_stream.update("message_count", partial: "messages/count"),
turbo_stream.replace("new_message", partial: "messages/form",
locals: { message: Message.new })
]
end
format.html { redirect_to messages_path, notice: "Message created" }
else
format.turbo_stream do
render turbo_stream: turbo_stream.replace(
"new_message",
partial: "messages/form",
locals: { message: @message }
)
end
format.html { render :new, status: :unprocessable_entity }
end
end
end
def destroy
@message = Message.find(params[:id])
@message.destroy
respond_to do |format|
# Removal with animation
format.turbo_stream { render turbo_stream: turbo_stream.remove(@message) }
format.html { redirect_to messages_path, notice: "Message deleted" }
end
end
end
```
### Template Turbo Stream Khusus
Untuk respons yang lebih kompleks, template `.turbo_stream.erb` menawarkan fleksibilitas lebih.
```erb
<%= turbo_stream.prepend "messages" do %>
<%= render @message %>
<% end %>
<%= turbo_stream.update "message_count" do %>
<%= Message.count %> messages
<% end %>
<%= turbo_stream.replace "new_message" do %>
<%= render "form", message: Message.new %>
<% end %>
<%= turbo_stream.prepend "flash_messages" do %>
Message sent successfully!
<% end %>
```
> **Format Respons**
>
> Respons Turbo Stream harus memiliki content-type `text/vnd.turbo-stream.html`. Rails menangani ini secara otomatis dengan `respond_to` dan format `turbo_stream`.
## Broadcasting Real-Time dengan Action Cable
Turbo Streams benar-benar bersinar dengan broadcasting WebSocket. Pengguna menerima pembaruan secara instan tanpa polling.
```ruby
# app/models/message.rb
class Message < ApplicationRecord
belongs_to :room
belongs_to :author, class_name: "User"
# Automatic broadcast after creation
after_create_commit do
broadcast_append_to(
room,
target: "messages",
partial: "messages/message",
locals: { message: self }
)
end
# Broadcast after update
after_update_commit do
broadcast_replace_to(
room,
target: dom_id(self),
partial: "messages/message",
locals: { message: self }
)
end
# Broadcast after deletion
after_destroy_commit do
broadcast_remove_to(room, target: dom_id(self))
end
end
```
View berlangganan ke stream yang sesuai dengan helper `turbo_stream_from`.
```erb
<%= form_with model: [@room, Message.new], id: "new_message" do |f| %>
<%= f.text_area :content, placeholder: "Your message..." %>
<%= f.submit "Send" %>
<% end %>
```
### Broadcasting Asinkron untuk Operasi Berat
Untuk menghindari pemblokiran permintaan, broadcasting dapat dilakukan di latar belakang.
```ruby
# app/models/report.rb
class Report < ApplicationRecord
belongs_to :user
after_create_commit :generate_async
private
def generate_async
GenerateReportJob.perform_later(self)
end
end
```
```ruby
# app/jobs/generate_report_job.rb
class GenerateReportJob < ApplicationJob
queue_as :default
def perform(report)
# Simulating a long operation
report.update!(status: "processing")
# Notify user of start
report.broadcast_replace_to(
report.user, :reports,
target: dom_id(report),
partial: "reports/report"
)
# Generate the report
result = ReportGenerator.new(report).generate
report.update!(content: result, status: "completed")
# Notify user of completion
report.broadcast_replace_to(
report.user, :reports,
target: dom_id(report),
partial: "reports/report"
)
end
end
```
## Pola Lanjutan dengan Turbo
### Pengeditan Formulir Inline
Pola umum melibatkan penggantian konten statis dengan formulir edit inline.
```erb
<% end %>
```
### Navigasi di Luar Frame Induk
Secara default, tautan dalam frame tetap dalam frame tersebut. Atribut `data-turbo-frame` memungkinkan penargetan frame lain atau seluruh halaman.
```erb
```
```erb
<%= yield %>
```
> **Debugging Turbo**
>
> Di lingkungan development, buka konsol JavaScript dan ketik `Turbo.setProgressBarDelay(0)` untuk melihat progress bar secara langsung. `Turbo.session.drive = false` menonaktifkan Turbo Drive sementara.
## Penanganan Error dan Status Loading
UX yang baik memerlukan penanganan status loading dan error jaringan.
```erb
<%= form_with model: @model,
data: {
controller: "form-loading",
action: "turbo:submit-start->form-loading#disable turbo:submit-end->form-loading#enable"
} do |f| %>
<%= yield f %>
<% end %>
```
```javascript
// app/javascript/controllers/form_loading_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["submit", "text", "spinner"]
disable() {
this.submitTarget.disabled = true
this.textTarget.classList.add("hidden")
this.spinnerTarget.classList.remove("hidden")
}
enable() {
this.submitTarget.disabled = false
this.textTarget.classList.remove("hidden")
this.spinnerTarget.classList.add("hidden")
}
}
```
### Penanganan Error Server
```ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound do |exception|
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.replace(
"main_content",
partial: "shared/not_found"
)
end
format.html { render "errors/not_found", status: :not_found }
end
end
end
```
## Optimasi Performa
Beberapa teknik memastikan aplikasi Turbo yang performan.
```ruby
# app/controllers/messages_controller.rb
class MessagesController < ApplicationController
# Avoid N+1 queries with eager loading
def index
@messages = Message.includes(:author, :room)
.order(created_at: :desc)
.page(params[:page])
end
# Fragment caching for static elements
def show
@message = Message.find(params[:id])
fresh_when @message
end
end
```
```erb
<% cache message do %>
<%= message.content %>
By <%= message.author.name %>
<% end %>
```
## Kesimpulan
Hotwire dan Turbo mentransformasi pengembangan Rails dengan menghilangkan kompleksitas framework JavaScript tradisional. Turbo Drive mempercepat navigasi, Turbo Frames memungkinkan pembaruan tertarget, dan Turbo Streams menawarkan kemampuan real-time yang powerful.
### Checklist untuk Memulai dengan Hotwire
- Gunakan Rails 7+ untuk integrasi Hotwire bawaan
- Pahami tiga komponen: Drive, Frames, dan Streams
- Identifikasi zona halaman yang diuntungkan dari Turbo Frames
- Gunakan `respond_to` dengan `format.turbo_stream` di controller
- Implementasikan broadcasting untuk fitur real-time
- Kombinasikan dengan Stimulus untuk interaksi JavaScript sederhana
Pendekatan "HTML melalui kabel" ini memungkinkan pembangunan aplikasi modern dan reaktif sambil mempertahankan kesederhanaan dan produktivitas yang membuat Ruby on Rails begitu powerful. Hasilnya: lebih sedikit kode yang perlu dipelihara, performa yang sangat baik, dan pengalaman developer yang optimal.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/id/blog/ruby-on-rails/ruby-on-rails-7-hotwire-turbo