# 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

<%= message.content %>

<%= link_to "Edit", edit_message_path(message) %> <%= button_to "Delete", message_path(message), method: :delete %>
``` ### 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

<%= @room.name %>

<%= turbo_stream_from @room %>
<%= render @room.messages.order(created_at: :asc) %>
<%= 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
<%= task.content %>
<%= link_to "Edit", edit_task_path(task) %> <%= button_to "Delete", task_path(task), method: :delete, data: { turbo_confirm: "Delete this task?" } %>
``` ```erb <%= form_with model: @task do |f| %> <%= f.text_field :content, autofocus: true %>
<%= f.submit "Save" %> <%= link_to "Cancel", task_path(@task) %>
<% 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