Turbo (Rails/Hotwire)
Server-side rendering acceleration framework for Rails — makes multi-page Rails apps feel like SPAs without writing JavaScript. Turbo has three parts: Turbo Drive (intercepts link clicks/form submits, replaces <body> via AJAX — no full page reload), Turbo Frames (<turbo-frame id='agents'> loads frame content independently), and Turbo Streams (server-side HTML fragments pushed via WebSocket or SSE to update specific page elements). Server broadcasts: Turbo::StreamsChannel broadcasts DOM updates from ActiveRecord callbacks. <%= turbo_stream.append 'agents', @agent %> appends new agent to list in real-time. Core of Rails 7+ Hotwire stack. Works with Action Cable for real-time agent updates.
Score Breakdown
⚙ Agent Friendliness
🔒 Security
Turbo Stream broadcasts don't auto-scope to current user — ensure broadcast streams use user-specific keys for private agent data. Turbo Drive preserves page HTML in history; sensitive agent data in DOM may persist in browser history cache. CSRF protection: Turbo includes Rails CSRF token in XHR requests automatically for agent form submissions.
⚡ Reliability
Best When
You're building a Rails agent app and want real-time UI updates (new agents appearing, status changes) and SPA-like navigation without building a separate JavaScript frontend — Turbo + Action Cable enables reactive agent UIs in pure Rails.
Avoid When
Your agent UI needs complex client-side state, offline capability, or your team is more comfortable with React/Vue than server-rendered HTML.
Use Cases
- • Agent list real-time updates — after_create_commit { broadcast_append_to 'agents' } on Agent model pushes new agent HTML to all subscribers; <%= turbo_stream_from 'agents' %> in view subscribes to updates; new agents appear in real-time list without page refresh
- • Turbo Frame for agent detail panel — <turbo-frame id='agent-detail'> lazy-loads agent details on click; clicking agent row fetches agent detail view and replaces only the frame content; SPA-like navigation without JavaScript routing
- • Agent form submission with validation — Turbo intercepts form POST; controller returns Turbo Stream on success (appends agent) or 422 with re-rendered form on validation error; user sees inline validation without page reload
- • Agent bulk operations — turbo_stream.remove 'agent-#{agent.id}' removes deleted agent from list; turbo_stream.replace 'agent-status-badge', partial: 'agents/status', locals: { agent: @agent } updates agent status indicator in real-time across all open sessions
- • Turbo Drive for agent navigation — all link clicks become XHR requests; Rails layout rendered once; page content swapped; back/forward work normally; agent app feels fast without React/Vue bundle complexity
Not For
- • Complex client-side state management — Turbo renders server HTML; for agent UIs with complex client state (real-time collaborative editing, drag-drop with optimistic updates) use React/Vue
- • Offline-capable PWAs — Turbo requires server for every interaction; for agent apps needing offline support use service workers with React
- • Non-Rails server frameworks — turbo-rails gem integrates with Rails; for Django/FastAPI/Express backends use the standalone turbo JS library without Rails-specific helpers
Interface
Authentication
No auth — Rails frontend framework. Broadcasts use Action Cable authentication (connection.rb current_user). Turbo Drive respects Rails session/cookie auth transparently.
Pricing
Turbo (turbo-rails gem and @hotwired/turbo npm package) is MIT licensed. Free for all use.
Agent Metadata
Known Gotchas
- ⚠ Forms must respond with 422 for validation errors — Turbo expects 422 Unprocessable Entity for invalid form submissions to re-render the form with errors; responding with 200 on validation failure causes Turbo to redirect (Turbo Drive behavior), losing error display; agent controllers using render :new on validation must include status: :unprocessable_entity
- ⚠ Turbo Frame id must match server and client — <turbo-frame id='agent-detail'> in HTML must match turbo_frame_tag 'agent-detail' in response; id mismatch causes frame to silently not update; case-sensitive; agent view refactors changing frame IDs break navigation without obvious error
- ⚠ broadcast_* methods require Redis in production — Turbo Streams broadcasts use Action Cable which needs Redis adapter for multi-process/multi-server agent deployments; default async adapter only works single-process; missing Redis causes broadcasts to silently not reach clients in multi-Puma-worker agent deployments
- ⚠ Turbo Drive breaks JavaScript that runs on page load — third-party scripts using DOMContentLoaded or window.onload don't re-fire on Turbo navigation; agent analytics (Google Analytics pageview) or ad scripts miss subsequent page views; use document.addEventListener('turbo:load') instead of DOMContentLoaded for agent scripts
- ⚠ Turbo Frame link clicks stay inside frame by default — all links inside <turbo-frame> load response into same frame; to navigate outside frame, add data-turbo-frame='_top'; agent detail pages with 'Edit' links open edit form inside detail frame unless data-turbo-frame='_top' added to edit links
- ⚠ broadcast_to authorization not automatic — broadcast_append_to 'agents' broadcasts to ALL subscribers of 'agents' stream; agent broadcasts are not scoped to authorized users by default; use broadcast_append_to @agent.user or stream_from 'agent_#{current_user.id}' for agent user-scoped real-time updates; global streams expose agent data across users
Alternatives
Full Evaluation Report
Detailed scoring breakdown, competitive positioning, security analysis, and improvement recommendations for Turbo (Rails/Hotwire).
Scores are editorial opinions as of 2026-03-06.