# Ruby on Rails 7 : Hotwire et Turbo pour des applications réactives > Guide complet sur Hotwire et Turbo dans Rails 7. Apprenez à créer des applications réactives sans écrire de JavaScript avec Turbo Drive, Frames et Streams. - Published: 2026-01-11 - Updated: 2026-03-28 - Author: SharpSkill - Tags: ruby on rails, hotwire, turbo, turbo frames, turbo streams - Reading time: 12 min --- Rails 7 a révolutionné le développement web en intégrant Hotwire par défaut. Cette stack permet de créer des applications hautement réactives sans écrire une ligne de JavaScript custom. Turbo Drive, Turbo Frames et Turbo Streams remplacent les approches SPA traditionnelles par une philosophie "HTML over the wire". > **Pourquoi Hotwire ?** > > Hotwire réduit drastiquement la complexité frontend. Plus besoin de React ou Vue pour des interfaces dynamiques : le serveur envoie du HTML prêt à l'emploi, et Turbo gère les mises à jour du DOM automatiquement. ## Comprendre l'architecture Hotwire Hotwire se compose de trois technologies complémentaires qui travaillent ensemble pour offrir une expérience utilisateur fluide sans la complexité habituelle des frameworks JavaScript. **Turbo Drive** accélère la navigation en interceptant les clics sur les liens et les soumissions de formulaires. Au lieu de recharger toute la page, seul le contenu du `` est remplacé, préservant le contexte JavaScript et CSS. **Turbo Frames** décomposent les pages en sections indépendantes. Chaque frame peut être mise à jour séparément, permettant des interactions ciblées sans affecter le reste de la page. **Turbo Streams** permettent des mises à jour en temps réel via WebSocket ou en réponse aux requêtes HTTP. Huit actions CRUD sont disponibles pour manipuler le DOM de manière déclarative. ```ruby # Gemfile # Installation de Turbo Rails (inclus par défaut dans Rails 7+) gem 'turbo-rails' ``` Pour les projets existants, l'installation se fait en une commande. ```bash # installation.sh # Installation de Turbo dans un projet Rails existant rails turbo:install # Vérifie que l'import JavaScript est présent cat app/javascript/application.js # Devrait contenir : import "@hotwired/turbo-rails" ``` ## Configuration initiale de Turbo Drive Turbo Drive est activé par défaut dans Rails 7. Toute navigation devient automatiquement "turbo" sans configuration supplémentaire. Le comportement peut être personnalisé via des attributs data. ```erb <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> <%= javascript_importmap_tags %>
<%= yield %> ``` Turbo Drive peut être désactivé sur des liens ou formulaires spécifiques quand nécessaire. ```erb <%= link_to "Dashboard", dashboard_path %> <%= link_to "Télécharger PDF", export_path, data: { turbo: false } %> <%= form_with model: @document, data: { turbo: false } do |f| %> <%= f.file_field :attachment %> <%= f.submit "Uploader" %> <% end %> <%= link_to "Supprimer", item_path(@item), data: { turbo_method: :delete, turbo_confirm: "Êtes-vous sûr ?" } %> ``` > **Cache Turbo Drive** > > Turbo Drive met en cache les pages visitées. L'attribut `data-turbo-track="reload"` sur les assets force un rechargement complet si les fichiers CSS/JS changent. ## Turbo Frames pour les mises à jour ciblées Les Turbo Frames permettent de définir des zones de la page qui se mettent à jour indépendamment. Chaque frame possède un identifiant unique et ne réagit qu'aux réponses contenant un frame correspondant. ```erb

Messages

<%= render @messages %>
<%= link_to "Charger plus", messages_path(page: @next_page) %>
<%= render "form", message: Message.new %> ``` La réponse du serveur doit contenir un frame avec le même identifiant pour que la mise à jour fonctionne. ```erb

<%= message.content %>

<%= link_to "Éditer", edit_message_path(message) %> <%= button_to "Supprimer", message_path(message), method: :delete %>
``` ### Chargement différé avec Turbo Frames Les frames peuvent charger leur contenu de manière asynchrone grâce à l'attribut `src`. Le contenu initial est affiché pendant le chargement. ```erb

Tableau de bord

Chargement des statistiques...

Chargement des notifications...

<%= render "activity/placeholder" %> ``` Le contrôleur répond avec une vue contenant uniquement le frame demandé. ```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 # Rendu partiel pour le frame render partial: "dashboard/stats" end end ``` ```erb

<%= @user_count %>

Utilisateurs

<%= @message_count %>

Messages

<%= @active_today %>

Actifs aujourd'hui

``` ## Turbo Streams pour les mises à jour en temps réel Turbo Streams offrent huit actions pour manipuler le DOM : `append`, `prepend`, `replace`, `update`, `remove`, `before`, `after` et `morph`. Ces actions peuvent être déclenchées en réponse HTTP ou via 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 # Réponse Turbo Stream pour ajouter le 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 créé" } 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| # Suppression avec animation format.turbo_stream { render turbo_stream: turbo_stream.remove(@message) } format.html { redirect_to messages_path, notice: "Message supprimé" } end end end ``` ### Templates Turbo Stream dédiés Pour des réponses plus complexes, un template `.turbo_stream.erb` offre plus de flexibilité. ```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 envoyé avec succès !
<% end %> ``` > **Format de réponse** > > Les réponses Turbo Stream doivent avoir le content-type `text/vnd.turbo-stream.html`. Rails le gère automatiquement avec `respond_to` et le format `turbo_stream`. ## Broadcasting en temps réel avec Action Cable Turbo Streams brillent vraiment avec le broadcasting WebSocket. Les utilisateurs reçoivent les mises à jour instantanément sans polling. ```ruby # app/models/message.rb class Message < ApplicationRecord belongs_to :room belongs_to :author, class_name: "User" # Broadcast automatique après création after_create_commit do broadcast_append_to( room, target: "messages", partial: "messages/message", locals: { message: self } ) end # Broadcast après mise à jour after_update_commit do broadcast_replace_to( room, target: dom_id(self), partial: "messages/message", locals: { message: self } ) end # Broadcast après suppression after_destroy_commit do broadcast_remove_to(room, target: dom_id(self)) end end ``` La vue s'abonne au stream correspondant avec le 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: "Votre message..." %> <%= f.submit "Envoyer" %> <% end %> ``` ### Broadcast asynchrone pour les opérations lourdes Pour éviter de bloquer les requêtes, le broadcast peut être effectué en arrière-plan. ```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) # Simulation d'une opération longue report.update!(status: "processing") # Notifie l'utilisateur du début report.broadcast_replace_to( report.user, :reports, target: dom_id(report), partial: "reports/report" ) # Génération du rapport result = ReportGenerator.new(report).generate report.update!(content: result, status: "completed") # Notifie l'utilisateur de la fin report.broadcast_replace_to( report.user, :reports, target: dom_id(report), partial: "reports/report" ) end end ``` ## Patterns avancés avec Turbo ### Formulaire inline avec édition Un pattern courant consiste à remplacer du contenu statique par un formulaire d'édition inline. ```erb
<%= task.content %>
<%= link_to "Éditer", edit_task_path(task) %> <%= button_to "Supprimer", task_path(task), method: :delete, data: { turbo_confirm: "Supprimer cette tâche ?" } %>
``` ```erb <%= form_with model: @task do |f| %> <%= f.text_field :content, autofocus: true %>
<%= f.submit "Sauvegarder" %> <%= link_to "Annuler", task_path(@task) %>
<% end %>
``` ### Navigation hors du frame parent Par défaut, les liens dans un frame restent dans ce frame. L'attribut `data-turbo-frame` permet de cibler un autre frame ou la page entière. ```erb ``` ```erb
<%= yield %>
``` > **Debugging Turbo** > > En développement, ouvrez la console JavaScript et tapez `Turbo.setProgressBarDelay(0)` pour voir immédiatement la barre de progression. `Turbo.session.drive = false` désactive Turbo Drive temporairement. ## Gestion des erreurs et états de chargement Une bonne UX nécessite de gérer les états de chargement et les erreurs réseau. ```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") } } ``` ### Gestion des erreurs serveur ```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 ``` ## Optimisation des performances Quelques techniques pour des applications Turbo performantes. ```ruby # app/controllers/messages_controller.rb class MessagesController < ApplicationController # Évite les requêtes N+1 avec eager loading def index @messages = Message.includes(:author, :room) .order(created_at: :desc) .page(params[:page]) end # Cache fragment pour les éléments statiques def show @message = Message.find(params[:id]) fresh_when @message end end ``` ```erb <% cache message do %>
<%= message.content %> Par <%= message.author.name %>
<% end %> ``` ## Conclusion Hotwire et Turbo transforment le développement Rails en éliminant la complexité des frameworks JavaScript traditionnels. Turbo Drive accélère la navigation, Turbo Frames permettent des mises à jour ciblées, et Turbo Streams offrent des capacités temps réel puissantes. ### Checklist pour bien démarrer avec Hotwire - ✅ Utiliser Rails 7+ pour avoir Hotwire intégré par défaut - ✅ Comprendre les trois composants : Drive, Frames et Streams - ✅ Identifier les zones de la page qui bénéficient de Turbo Frames - ✅ Utiliser `respond_to` avec `format.turbo_stream` dans les contrôleurs - ✅ Implémenter le broadcasting pour les fonctionnalités temps réel - ✅ Combiner avec Stimulus pour les interactions JavaScript simples Cette approche "HTML over the wire" permet de construire des applications modernes et réactives tout en gardant la simplicité et la productivité qui font la force de Ruby on Rails. Le résultat : moins de code à maintenir, des performances excellentes, et une expérience développeur optimale. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/ruby-on-rails/ruby-on-rails-7-hotwire-turbo