Rails Turbo dan Hotwire 2026: Membangun Aplikasi Real-Time dan Persiapan Interview

Panduan lengkap Rails Turbo 8, Hotwire, dan Turbo Streams untuk membangun aplikasi real-time dengan contoh kode praktis.

Rails Turbo dan Hotwire 2026: Membangun Aplikasi Real-Time dan Persiapan Interview

Rails Turbo dan Hotwire menghadirkan interaktivitas real-time ke aplikasi web tanpa memerlukan framework JavaScript kustom. Ekosistem 2026 telah berkembang secara signifikan, dengan kemampuan morphing Turbo 8 dan broadcasting Rails 8 yang ditingkatkan membuat UI reaktif dapat dicapai dengan kode minimal. Artikel ini membahas pola-pola produksi, persiapan interview, dan detail implementasi untuk membangun aplikasi Rails modern.

Poin Penting

Morphing Turbo 8 dengan broadcasts_refreshes memungkinkan pembaruan real-time hanya dengan satu baris kode Ruby. Server melakukan broadcast sinyal, klien melakukan morph DOM, dan posisi scroll dipertahankan secara otomatis.

Memahami Arsitektur Hotwire di Tahun 2026

Hotwire terdiri dari tiga teknologi komplementer yang bekerja bersama untuk menciptakan pengalaman web reaktif. Turbo Drive mencegat navigasi dan pengiriman formulir, mengganti konten halaman melalui AJAX tanpa reload penuh. Turbo Frames membatasi pembaruan ke region halaman tertentu, memungkinkan editing inline dan lazy loading. Turbo Streams mengirimkan perubahan DOM secara granular melalui koneksi WebSocket atau respons HTTP.

Arsitektur ini mengikuti model server-centric di mana HTML tetap menjadi payload utama. Berbeda dengan SPA yang mentransfer JSON dan merender di klien, aplikasi Hotwire merender HTML di sisi server dan mengalirkannya ke browser. Pendekatan ini menyederhanakan manajemen state, meningkatkan SEO, dan mengurangi ukuran bundle JavaScript.

Rails 8 secara default dilengkapi dengan Hotwire 2.0, mengintegrasikan Action Cable untuk dukungan WebSocket. Framework ini menangani manajemen koneksi, subscription channel, dan broadcasting tanpa konfigurasi manual.

Turbo Drive: Navigasi SPA Tanpa Konfigurasi

Turbo Drive mengubah link dan formulir standar menjadi interaksi yang didukung AJAX. Ketika diaktifkan, mengklik link akan memicu fetch request, body respons menggantikan konten halaman saat ini, dan riwayat browser diperbarui dengan tepat.

ruby
# app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <%= turbo_refreshes_with method: :morph, scroll: :preserve %>
    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Atribut data-turbo-track: "reload" memastikan perubahan asset memicu reload halaman penuh, mencegah CSS atau JavaScript yang usang. Helper turbo_refreshes_with mengonfigurasi perilaku morphing secara global, meskipun dapat di-override per halaman.

Menonaktifkan Turbo Drive untuk link tertentu memerlukan atribut data-turbo:

erb
<!-- Nonaktifkan Turbo untuk link eksternal -->
<%= link_to "Download PDF", document_path(@doc, format: :pdf), data: { turbo: false } %>

<!-- Paksa reload halaman penuh -->
<%= link_to "Admin Panel", admin_path, data: { turbo_action: "replace" } %>

Turbo Frames: Pembaruan Halaman Terskop

Turbo Frames mengisolasi bagian halaman menjadi region yang dapat diperbarui secara independen. Frame membungkus konten dengan identifier unik, dan navigasi apa pun dalam frame tersebut hanya memperbarui konten yang dibungkus.

erb
<!-- app/views/comments/index.html.erb -->
<%= turbo_frame_tag "comments" do %>
  <% @comments.each do |comment| %>
    <%= render comment %>
  <% end %>
  
  <%= link_to "Muat lebih banyak", comments_path(page: @page + 1) %>
<% end %>

Respons server harus menyertakan tag frame yang sesuai. Turbo mengekstrak konten frame dan mengganti frame yang ada di DOM:

erb
<!-- app/views/comments/index.html.erb (respons terpaginasi) -->
<%= turbo_frame_tag "comments" do %>
  <% @comments.each do |comment| %>
    <%= render comment %>
  <% end %>
  
  <% if @comments.any? %>
    <%= link_to "Muat lebih banyak", comments_path(page: @page + 1) %>
  <% end %>
<% end %>

Lazy loading frames menunda pemuatan konten hingga frame memasuki viewport:

erb
<%= turbo_frame_tag "user_stats", src: user_stats_path(@user), loading: :lazy do %>
  <div class="animate-pulse bg-gray-200 h-24"></div>
<% end %>

Turbo Streams: Manipulasi DOM Real-Time

Turbo Streams menyediakan tujuh aksi untuk memanipulasi DOM: append, prepend, replace, update, remove, before, dan after. Setiap aksi menargetkan elemen berdasarkan ID dan menerapkan perubahan yang ditentukan.

erb
<!-- app/views/comments/create.turbo_stream.erb -->
<%= turbo_stream.prepend "comments" do %>
  <%= render @comment %>
<% end %>

<%= turbo_stream.update "comment_count" do %>
  <%= @comments_count %> komentar
<% end %>

<%= turbo_stream.replace "new_comment_form" do %>
  <%= render "form", comment: Comment.new %>
<% end %>

Controller merespons request Turbo Stream bersamaan dengan HTML:

ruby
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  def create
    @comment = @post.comments.build(comment_params)
    
    respond_to do |format|
      if @comment.save
        format.turbo_stream
        format.html { redirect_to @post, notice: "Komentar ditambahkan" }
      else
        format.turbo_stream do
          render turbo_stream: turbo_stream.replace(
            "new_comment_form",
            partial: "form",
            locals: { comment: @comment }
          )
        end
        format.html { render :new, status: :unprocessable_entity }
      end
    end
  end
end

Siap menguasai wawancara Ruby on Rails Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Turbo 8 Morphing: Pembaruan DOM Cerdas

Turbo 8 memperkenalkan morphing, didukung oleh library idiomorph. Alih-alih mengganti seluruh bagian halaman, morphing membandingkan DOM saat ini dengan HTML yang masuk dan menerapkan perubahan minimal. Ini mempertahankan state formulir, posisi scroll, dan fokus.

ruby
# app/models/message.rb
class Message < ApplicationRecord
  belongs_to :conversation
  
  broadcasts_refreshes
end

Callback broadcasts_refreshes menjadwalkan background job ketika record berubah. Job tersebut melakukan broadcast sinyal refresh ke semua klien yang berlangganan melalui Action Cable. Klien mengambil HTML halaman saat ini dan melakukan morph DOM mereka agar sesuai.

erb
<!-- app/views/conversations/show.html.erb -->
<%= turbo_stream_from @conversation %>

<div id="messages">
  <%= render @conversation.messages %>
</div>

<%= render "messages/form", conversation: @conversation %>

Helper turbo_stream_from membuat halaman berlangganan ke channel Turbo Streams. Ketika pesan apa pun dalam percakapan berubah, semua penonton melihat pembaruan secara instan.

Opsi Konfigurasi Morphing

Kontrol detail atas perilaku morphing menggunakan atribut data:

erb
<!-- Pertahankan elemen di seluruh morph -->
<div data-turbo-permanent id="video-player">
  <video src="<%= @video.url %>" autoplay></video>
</div>

<!-- Lewati morphing, selalu replace -->
<div data-turbo-method="replace" id="chart-container">
  <%= render "chart", data: @analytics %>
</div>

Debouncer refresh mencegah broadcast berlebihan. Ketika beberapa pembaruan database terjadi dengan cepat, hanya satu refresh yang dipicu setelah penundaan 500ms. Ini dapat dikonfigurasi per model:

ruby
# app/models/task.rb
class Task < ApplicationRecord
  broadcasts_refreshes debounce: 1.second
end

Pertanyaan Interview: Fundamental Hotwire

Interview teknis sering menilai pengetahuan Hotwire melalui pertanyaan konseptual dan implementasi.

Q: Bagaimana Turbo Drive berbeda dari library navigasi AJAX tradisional?

Turbo Drive mencegat semua navigasi secara otomatis tanpa konfigurasi eksplisit. Ini mempertahankan perilaku tombol back/forward browser, memperbarui URL melalui pushState, dan menangani redirect secara transparan. Perbedaan utamanya adalah progressive enhancement: aplikasi bekerja tanpa JavaScript, mendapatkan peningkatan kecepatan ketika JavaScript dimuat.

Q: Kapan Turbo Frames lebih disukai daripada Turbo Streams?

Turbo Frames cocok untuk interaksi berbasis navigasi di mana mengklik link harus memperbarui region halaman tertentu. Frames mempertahankan semantik URL dan bekerja dengan riwayat browser. Turbo Streams menangani skenario broadcast di mana beberapa elemen perlu diperbarui secara bersamaan atau di mana pembaruan berasal dari proses latar belakang daripada navigasi pengguna.

Q: Jelaskan siklus refresh morphing di Turbo 8.

Ketika broadcasts_refreshes dipicu, Rails memasukkan antrian Turbo::Streams::BroadcastStreamJob. Job tersebut melakukan broadcast aksi refresh ke nama stream yang diturunkan dari model. Klien yang berlangganan menerima aksi tersebut, mengambil halaman saat ini melalui request GET, dan meneruskan kedua DOM ke idiomorph. Library menghitung diff minimal dan menerapkan perubahan, mempertahankan elemen yang ditandai data-turbo-permanent.

Membangun Sistem Notifikasi Real-Time

Implementasi praktis mendemonstrasikan kemampuan Hotwire untuk fitur real-time:

ruby
# app/models/notification.rb
class Notification < ApplicationRecord
  belongs_to :user
  
  after_create_commit :broadcast_to_user
  
  private
  
  def broadcast_to_user
    broadcast_prepend_to(
      "notifications_#{user_id}",
      target: "notifications",
      partial: "notifications/notification",
      locals: { notification: self }
    )
    
    broadcast_update_to(
      "notifications_#{user_id}",
      target: "unread_count",
      html: user.notifications.unread.count.to_s
    )
  end
end

View berlangganan ke channel spesifik pengguna:

erb
<!-- app/views/layouts/_navbar.html.erb -->
<nav>
  <%= turbo_stream_from "notifications_#{current_user.id}" %>
  
  <div class="relative">
    <button>Notifikasi</button>
    <span id="unread_count" class="badge">
      <%= current_user.notifications.unread.count %>
    </span>
    
    <div id="notifications" class="dropdown">
      <%= render current_user.notifications.recent %>
    </div>
  </div>
</nav>

Ketika notifikasi dibuat di mana saja dalam sistem, navbar pengguna diperbarui secara instan tanpa polling.

Pola Optimasi Performa

Aplikasi produksi mendapat manfaat dari beberapa teknik optimasi yang didokumentasikan dalam panduan produksi Rails 8.

Eager Loading Subscription Stream

Hindari pola subscription N+1 dalam daftar:

ruby
# app/controllers/projects_controller.rb
def index
  @projects = current_user.projects.includes(:tasks)
end
erb
<!-- Subscribe sekali per proyek, bukan per task -->
<% @projects.each do |project| %>
  <%= turbo_stream_from project %>
  <%= render project %>
<% end %>

Broadcasting Selektif

Batasi broadcast ke perubahan yang relevan:

ruby
# app/models/post.rb
class Post < ApplicationRecord
  broadcasts_refreshes_to ->(post) { post.published? ? :published_posts : :draft_posts }
end

Caching dengan Morphing

Fragment caching bekerja bersamaan dengan morphing ketika cache keys menyertakan timestamp:

erb
<% cache [@conversation, @conversation.messages.maximum(:updated_at)] do %>
  <%= render @conversation.messages %>
<% end %>

Kendala Umum dan Solusinya

Beberapa masalah sering muncul saat mengimplementasikan aplikasi Hotwire.

Inisialisasi JavaScript: Controller Stimulus harus memperhitungkan morphing. Gunakan callback connect() dan disconnect() dengan benar, dan hindari menyimpan state yang akan diinvalidasi oleh morphing.

app/javascript/controllers/chart_controller.jsjavascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    this.chart = new Chart(this.element, this.chartConfig)
  }
  
  disconnect() {
    this.chart?.destroy()
  }
  
  get chartConfig() {
    return JSON.parse(this.element.dataset.chartConfig)
  }
}

Preservasi State Formulir: Morphing mempertahankan input formulir secara default. Untuk formulir yang harus direset setelah submission, hapus secara eksplisit dalam respons Turbo Stream:

erb
<%= turbo_stream.replace "message_form" do %>
  <%= render "form", message: Message.new %>
<% end %>

Lompatan Posisi Scroll: Opsi scroll: :preserve membantu, tetapi layout kompleks mungkin memerlukan scroll anchoring eksplisit:

erb
<div data-turbo-scroll="preserve" id="message_list">
  <%= render @messages %>
</div>

Integrasi Stimulus untuk Interaktivitas Lanjutan

Sementara Turbo menangani sebagian besar interaksi, Stimulus mengisi celah yang memerlukan logika sisi klien. Kombinasi ini menjaga JavaScript tetap fokus dan minimal.

app/javascript/controllers/auto_submit_controller.jsjavascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["form"]
  
  submit() {
    clearTimeout(this.timeout)
    this.timeout = setTimeout(() => {
      this.formTarget.requestSubmit()
    }, 300)
  }
}
erb
<div data-controller="auto-submit">
  <%= form_with model: @search, data: { auto_submit_target: "form" } do |f| %>
    <%= f.text_field :query, 
        data: { action: "input->auto-submit#submit" },
        placeholder: "Cari..." %>
  <% end %>
</div>

Untuk pola yang lebih lanjut, tinjau modul strategi caching dan pendekatan testing Rails.

Kesimpulan

  • Turbo Drive menyediakan navigasi seperti SPA tanpa konfigurasi dengan mencegat link dan formulir
  • Turbo Frames membatasi pembaruan ke region halaman, memungkinkan editing inline dan lazy loading
  • Turbo Streams melakukan broadcast perubahan DOM melalui tujuh aksi: append, prepend, replace, update, remove, before, after
  • Morphing Turbo 8 dengan broadcasts_refreshes mengirimkan pembaruan real-time menggunakan idiomorph untuk perubahan DOM minimal
  • Pertahankan elemen di seluruh morph dengan data-turbo-permanent
  • Gunakan turbo_stream_from di view yang dipasangkan dengan callback model untuk broadcasting
  • Debounce default 500ms mencegah broadcast berlebihan selama pembaruan cepat
  • Controller Stimulus harus menggunakan method lifecycle connect/disconnect yang tepat untuk kompatibilitas morphing
  • Fragment caching tetap efektif ketika cache keys menyertakan timestamp

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Bagikan

Artikel terkait