Using Turbo Streams for Live Rails Updates Without JavaScript
Turbo Streams declaratively updates page regions via server-rendered HTML, enabling live Rails features without JavaScript. This post covers the concept, a concrete comment-feed example, and trade-offs to watch.
06 Nov 2025, 10:53 UTC

Using Turbo Streams for Live Rails Updates Without JavaScript
Want live page updates without writing WebSocket handlers or polling endpoints? Hotwire's Turbo Streams lets you broadcast HTML fragments from the server and have the browser apply them automatically. This blog walks through the concept, a concrete implementation, and the trade-offs you'll hit in production.
How Turbo Streams work
Turbo Streams defines five HTML tags that the browser interprets to modify the DOM: <turbo-stream> wrapping an action and a target. The actions are append, prepend, replace, update, and remove. When a server renders a stream response, the browser parses it and performs the action against the identified element ID. No JavaScript needed on the client.
Worked example: live comment feed
- Generate a comment scaffold:
rails generate scaffold Comment body:text article:belongs_to - Add a Turbo Stream partial under
app/views/comments/_comment.html.erbthat renders a single comment wrapper with a unique ID. - In the comments controller, after creating a comment, broadcast a stream:
# app/controllers/comments_controller.rb
def create
@comment = Comment.new(comment_params)
if @comment.save
turbo_stream.broadcast(@comment.article, partial: "comments/comment", locals: { comment: @comment })
head :ok
else
render json: @comment.errors, status: :unprocessable_entity
end
end
Here, turbo_stream.broadcast sends a <turbo-stream> fragment over the active WebSocket (or SSE) connection. The browser locates the element with ID matching @comment.article and replaces its content with the partial.
Verification in practice
To confirm the stream works, start your Rails server and open two browser tabs pointing at the article's comment section. Open the WebSocket inspector or watch the network tab: you should see a text/html response with the <turbo-stream> fragment. The comment list in both tabs updates instantly without a full reload.
Required permissions: Read access to the app logs and network dev tools; no special privileges beyond standard development mode.
Expected check: The response Content-Type is text/html and the DOM element with the target ID reflects the new comment.
Relevant risk: If the target element ID is not unique or the partial is missing, the stream silently fails—check server logs for rendering errors.
Trade-offs and limitations
Turbo Streams assume the server renders HTML; if your stack heavily uses a client-side framework like React or Vue inside a Turbo frame, state may not survive a stream update. Search engines may not execute the stream navigation, so keep critical markup in the initial HTML response. Caching can serve stale comments if cache-control headers don't invalidate the article page after a broadcast; set Cache-Control: no-cache or use Turbo's turbo-refresh meta tag when appropriate.
Another common pitfall: broadcasting to a broad target overwrites user input. Always scope the stream to a specific frame ID or element, and validate server data before sending it to the browser.
Getting started
- Add the Hotwire Turbo gem (it's included by default in new Rails 7+ apps).
- Ensure
<meta name="turbo-visit-control" content="reload">is present in your layout to enable Turbo Drive and stream broadcasting. - Create a Turbo Stream broadcaster in any controller action using
turbo_stream.broadcast,turbo_stream.update, orturbo_stream.replace. - Test by submitting a form or triggering an action that broadcasts; open dev tools to verify the
<turbo-stream>tag and the resulting DOM change.
Turbo Streams can dramatically reduce JavaScript boilerplate, but they work best when you treat the server as the source of truth for HTML and keep client-side state in elements outside the streamed region. Start with a small page, verify the stream behavior, then expand to real-time features across your app.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.