Rails Turbo et Hotwire en 2026 : Applications Temps Réel et Questions d'Entretien

Maîtrisez Turbo 8 et Hotwire pour créer des applications Rails réactives. Guide complet sur les Turbo Streams, le morphing et les patterns de production pour entretiens techniques.

Rails Turbo et Hotwire en 2026 : Applications Temps Réel et Questions d'Entretien

Rails Turbo et Hotwire permettent de créer des applications web interactives en temps réel sans recourir à des frameworks JavaScript complexes. L'écosystème 2026 a considérablement évolué avec les capacités de morphing de Turbo 8 et les améliorations du broadcasting dans Rails 8. Ce guide approfondi couvre les patterns de production, la préparation aux entretiens techniques et les détails d'implémentation pour construire des applications Rails modernes.

Point Clé

Le morphing de Turbo 8 avec broadcasts_refreshes permet des mises à jour en temps réel avec une seule ligne de code Ruby. Le serveur diffuse un signal, les clients morphent le DOM, et la position de défilement est préservée automatiquement.

Comprendre l'Architecture Hotwire en 2026

Hotwire se compose de trois technologies complémentaires qui fonctionnent ensemble pour créer des expériences web réactives. Turbo Drive intercepte la navigation et les soumissions de formulaires, remplaçant le contenu de la page via AJAX sans rechargement complet. Turbo Frames limite les mises à jour à des régions spécifiques de la page, permettant l'édition inline et le chargement différé. Turbo Streams délivre des modifications granulaires du DOM via des connexions WebSocket ou des réponses HTTP.

L'architecture suit un modèle centré sur le serveur où le HTML reste le payload principal. Contrairement aux SPA qui transfèrent du JSON et effectuent le rendu côté client, les applications Hotwire rendent le HTML côté serveur et le diffusent aux navigateurs. Cette approche simplifie la gestion d'état, améliore le SEO et réduit la taille des bundles JavaScript.

Rails 8 intègre Hotwire 2.0 par défaut, avec Action Cable pour le support WebSocket. Le framework gère automatiquement les connexions, les souscriptions aux canaux et le broadcasting sans configuration manuelle.

Turbo Drive : Navigation SPA Sans Configuration

Turbo Drive transforme les liens et formulaires standards en interactions AJAX. Une fois activé, cliquer sur n'importe quel lien déclenche une requête fetch, le corps de la réponse remplace le contenu de la page actuelle, et l'historique du navigateur se met à jour correctement.

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>

L'attribut data-turbo-track: "reload" garantit que les changements d'assets déclenchent des rechargements complets de page, évitant les CSS ou JavaScript obsolètes. Le helper turbo_refreshes_with configure le comportement de morphing globalement, bien qu'il puisse être surchargé par page.

Désactiver Turbo Drive pour des liens spécifiques nécessite l'attribut data-turbo :

erb
<!-- Désactiver Turbo pour les liens externes -->
<%= link_to "Télécharger PDF", document_path(@doc, format: :pdf), data: { turbo: false } %>

<!-- Forcer un rechargement complet de page -->
<%= link_to "Panneau Admin", admin_path, data: { turbo_action: "replace" } %>

Turbo Frames : Mises à Jour de Page Scopées

Les Turbo Frames isolent des sections de page en régions indépendamment mises à jour. Un frame encapsule le contenu avec un identifiant unique, et toute navigation au sein de ce frame met à jour uniquement le contenu encapsulé.

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

La réponse du serveur doit inclure un tag frame correspondant. Turbo extrait le contenu du frame et remplace le frame existant dans le DOM :

erb
<!-- app/views/comments/index.html.erb (réponse paginée) -->
<%= turbo_frame_tag "comments" do %>
  <% @comments.each do |comment| %>
    <%= render comment %>
  <% end %>
  
  <% if @comments.any? %>
    <%= link_to "Charger plus", comments_path(page: @page + 1) %>
  <% end %>
<% end %>

Le chargement différé des frames reporte le chargement du contenu jusqu'à ce que le frame entre dans la fenêtre visible :

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 : Manipulation du DOM en Temps Réel

Turbo Streams fournit sept actions pour manipuler le DOM : append, prepend, replace, update, remove, before et after. Chaque action cible un élément par ID et applique le changement spécifié.

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

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

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

Les contrôleurs répondent aux requêtes Turbo Stream en plus du 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: "Commentaire ajouté" }
      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

Prêt à réussir tes entretiens Ruby on Rails ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Morphing Turbo 8 : Mises à Jour Intelligentes du DOM

Turbo 8 a introduit le morphing, propulsé par la bibliothèque idiomorph. Au lieu de remplacer des sections entières de page, le morphing compare le DOM actuel avec le HTML entrant et applique des changements minimaux. Cela préserve l'état des formulaires, la position de défilement et le focus.

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

Le callback broadcasts_refreshes planifie un job en arrière-plan quand les enregistrements changent. Le job diffuse un signal de rafraîchissement à tous les clients abonnés via Action Cable. Les clients récupèrent le HTML de la page actuelle et morphent leur DOM pour correspondre.

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

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

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

Le helper turbo_stream_from abonne la page à un canal Turbo Streams. Quand n'importe quel message de la conversation change, tous les visualiseurs voient les mises à jour instantanément.

Options de Configuration du Morphing

Un contrôle fin sur le comportement du morphing utilise des attributs data :

erb
<!-- Préserver l'élément à travers les morphs -->
<div data-turbo-permanent id="video-player">
  <video src="<%= @video.url %>" autoplay></video>
</div>

<!-- Ignorer le morphing, toujours remplacer -->
<div data-turbo-method="replace" id="chart-container">
  <%= render "chart", data: @analytics %>
</div>

Le debouncer de rafraîchissement empêche les diffusions excessives. Quand plusieurs mises à jour de base de données surviennent rapidement, un seul rafraîchissement se déclenche après un délai de 500ms. Ceci est configurable par modèle :

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

Questions d'Entretien : Fondamentaux Hotwire

Les entretiens techniques évaluent fréquemment les connaissances Hotwire à travers des questions conceptuelles et d'implémentation.

Q : Comment Turbo Drive diffère-t-il des bibliothèques de navigation AJAX traditionnelles ?

Turbo Drive intercepte automatiquement toute la navigation sans configuration explicite. Il préserve le comportement des boutons précédent/suivant du navigateur, met à jour l'URL via pushState, et gère les redirections de manière transparente. La distinction clé est l'amélioration progressive : les applications fonctionnent sans JavaScript, gagnant des améliorations de vitesse quand JavaScript se charge.

Q : Quand les Turbo Frames doivent-ils être préférés aux Turbo Streams ?

Les Turbo Frames conviennent aux interactions basées sur la navigation où cliquer sur un lien devrait mettre à jour une région spécifique de la page. Les frames maintiennent la sémantique des URL et fonctionnent avec l'historique du navigateur. Les Turbo Streams gèrent les scénarios de diffusion où plusieurs éléments nécessitent une mise à jour simultanée ou où les mises à jour proviennent de processus en arrière-plan plutôt que de la navigation utilisateur.

Q : Expliquez le cycle de rafraîchissement par morphing dans Turbo 8.

Quand broadcasts_refreshes se déclenche, Rails enfile Turbo::Streams::BroadcastStreamJob. Le job diffuse une action de rafraîchissement au nom de stream dérivé du modèle. Les clients abonnés reçoivent l'action, récupèrent la page actuelle via une requête GET, et passent les deux DOM à idiomorph. La bibliothèque calcule le diff minimal et applique les changements, préservant les éléments marqués data-turbo-permanent.

Construire un Système de Notifications en Temps Réel

Une implémentation pratique démontre les capacités de Hotwire pour les fonctionnalités temps réel :

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

La vue s'abonne au canal spécifique à l'utilisateur :

erb
<!-- app/views/layouts/_navbar.html.erb -->
<nav>
  <%= turbo_stream_from "notifications_#{current_user.id}" %>
  
  <div class="relative">
    <button>Notifications</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>

Quand une notification est créée n'importe où dans le système, la barre de navigation de l'utilisateur se met à jour instantanément sans polling.

Patterns d'Optimisation des Performances

Les applications de production bénéficient de plusieurs techniques d'optimisation documentées dans le guide de production Rails 8.

Chargement Eager des Souscriptions Stream

Évitez les patterns de souscription N+1 dans les listes :

ruby
# app/controllers/projects_controller.rb
def index
  @projects = current_user.projects.includes(:tasks)
end
erb
<!-- S'abonner une fois par projet, pas par tâche -->
<% @projects.each do |project| %>
  <%= turbo_stream_from project %>
  <%= render project %>
<% end %>

Diffusion Sélective

Limitez les diffusions aux changements pertinents :

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

Mise en Cache avec Morphing

La mise en cache de fragments fonctionne avec le morphing quand les clés de cache incorporent les timestamps :

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

Pièges Courants et Solutions

Plusieurs problèmes surviennent fréquemment lors de l'implémentation d'applications Hotwire.

Initialisation JavaScript : Les contrôleurs Stimulus doivent prendre en compte le morphing. Utilisez correctement les callbacks connect() et disconnect(), et évitez de stocker un état que le morphing invaliderait.

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)
  }
}

Préservation de l'État des Formulaires : Le morphing préserve les inputs de formulaire par défaut. Pour les formulaires qui doivent se réinitialiser après soumission, videz-les explicitement dans la réponse Turbo Stream :

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

Sauts de Position de Défilement : L'option scroll: :preserve aide, mais les layouts complexes peuvent nécessiter un ancrage de défilement explicite :

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

Intégration Stimulus pour une Interactivité Améliorée

Bien que Turbo gère la plupart des interactions, Stimulus comble les lacunes nécessitant une logique côté client. La combinaison garde le JavaScript focalisé et 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: "Rechercher..." %>
  <% end %>
</div>

Pour des patterns plus avancés, consultez les modules stratégies de mise en cache et approches de test Rails.

Conclusion

  • Turbo Drive fournit une navigation de type SPA sans configuration en interceptant les liens et formulaires
  • Les Turbo Frames scopent les mises à jour à des régions de page, permettant l'édition inline et le chargement différé
  • Les Turbo Streams diffusent les changements DOM via sept actions : append, prepend, replace, update, remove, before, after
  • Le morphing Turbo 8 avec broadcasts_refreshes délivre des mises à jour temps réel en utilisant idiomorph pour des changements DOM minimaux
  • Préservez les éléments à travers les morphs avec data-turbo-permanent
  • Utilisez turbo_stream_from dans les vues couplé avec les callbacks de modèle pour la diffusion
  • Le debounce par défaut de 500ms empêche les diffusions excessives pendant les mises à jour rapides
  • Les contrôleurs Stimulus doivent utiliser les méthodes de cycle de vie connect/disconnect appropriées pour la compatibilité avec le morphing
  • La mise en cache de fragments reste efficace quand les clés de cache incorporent les timestamps

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Tags

#rails turbo
#hotwire rails
#rails temps réel
#turbo streams tutoriel

Partager

Articles similaires