Choosing Between Turbo Frames and Turbo Streams for Partial Page Updates
Learn when to pick Turbo Frames or Turbo Streams for partial page updates, see a concise comparison table, and view concrete Rails‑style implementation snippets.
29 Jul 2025, 03:34 UTC

Decision: When to use Turbo Frames vs Turbo Streams
You need to update part of a page without a full reload while keeping JavaScript minimal, staying compatible with server‑side rendering, and preserving predictable navigation behavior. The choice depends on how many DOM elements must change and whether you want automatic history integration.
Comparison Table
| Feature | Turbo Frames | Turbo Streams |
|---|---|---|
| Scope | Single DOM element replacement | Multiple, arbitrary DOM mutations |
| Setup | Declare <turbo-frame id> and link/form with data-turbo-frame |
Stream from controller via <turbo-stream> actions (append, prepend, replace, etc.) |
| Navigation | Automatic frame navigation preserves state and updates URL | Requires explicit stream actions; no built‑in history integration |
| Typical use | Inline forms, lazy‑loaded tabs, click‑to‑load sections | Real‑time lists, notifications, batch updates |
Trade‑offs
Turbo Frames shine when you only need to swap one container. The frame mechanism automatically updates the browser history, so the back/forward buttons work without extra code. The downside is that you cannot change more than one element in a single response; attempting to do so will be ignored or cause unexpected DOM state.
Turbo Streams give you full control over the DOM: you can append items to a list, replace several sections, or insert notifications all at once. This flexibility comes with responsibility—you must manage stream actions yourself, ensure the target element exists when the stream runs, and you do not get automatic history updates. If you need history integration with Streams, you must combine them with Turbo Frame navigation or manually push state.
Implementation Example
The following shows a Rails‑style example that you can adapt to any server‑side framework that renders Turbo responses.
Using Turbo Frames (single‑element update)
- In your view, place a frame where the content will appear:
- Provide a link or form that targets the frame:
- When the link is clicked, Turbo intercepts the request, renders the response, and replaces only the
<turbo-frame id="comments">element. The URL updates to reflect the new resource, and the browser’s back/forward buttons restore the previous frame state automatically.
<!-- app/views/posts/show.html.erb -->
<turbo-frame id="comments">
<%= render @post.comments %>
</turbo-frame>
<%= link_to 'Show comments', post_comments_path(@post),
data: { turbo_frame: 'comments' } %>
Using Turbo Streams (multiple‑element update)
- In the controller action that creates a comment, respond with a Turbo Stream:
- Ensure the target element exists in the view before the stream runs:
- When the form is submitted, Turbo processes the stream, appends the new comment markup inside the
#commentsdiv, and leaves the rest of the page untouched. No URL change occurs; navigation does not replay this stream.
# app/controllers/comments_controller.rb
def create
@comment = @post.comments.build(comment_params)
if @comment.save
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.append('comments',
partial: 'comments/comment',
locals: { comment: @comment })
end
end
else
# handle validation errors …
end
end
<!-- app/views/posts/show.html.erb -->
<div id="comments">
<%= render @post.comments %>
</div>
Validation and Risks
To confirm Frame behavior, open the browser’s DevTools Network tab, click a Frame‑linked link, and verify that only the framed element is replaced while the URL updates without a full page reload. For Streams, trigger an action that broadcasts a Stream (e.g., submit a comment form) and inspect the page to see that the targeted element updates via the appropriate turbo‑stream action while other parts remain unchanged. Check that the back/forward buttons restore the previous Frame state; Streams do not affect history, so navigation should not replay Stream updates.
Risk: Avoid mixing Frame and Stream updates on the same element within a single request. Doing so can lead to unpredictable DOM state because the frame replacement and stream mutation may conflict. If you need both behaviors, split the work into separate requests or use a Frame for navigation and Streams only for auxiliary updates that target different elements.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.