Reducing Page Flicker with Turbo Frames in Rails
Learn how to eliminate full-page reloads in Ruby on Rails using Turbo Frames to create a snappier, SPA-like experience without leaving the server-side ecosystem.
27 Aug 2026, 18:18 UTC

The Problem: The Full-Page Reload Fatigue
When building a web application, there is a frustrating gap between a traditional multi-page application (MPA) and a single-page application (SPA). In a standard Rails app, clicking a "Edit" button or submitting a small form often triggers a full-page reload. This causes the browser to re-render the header, footer, and navigation, resulting in a visible "flicker" and a loss of scroll position that makes the app feel sluggish.
The goal is to update only the specific piece of the UI that changed—like a comment section or a user profile field—without writing a complex JavaScript frontend or managing a JSON API. Turbo Frames solve this by allowing the server to send a partial HTML fragment that the browser swaps into a specific container automatically.
How Turbo Frames Isolate State
A Turbo Frame is essentially a named container in your HTML. When a link or form inside a <turbo-frame> is clicked, Turbo intercepts the request and sends it to the server. Instead of replacing the entire body of the page, Turbo looks for a matching <turbo-frame> with the same ID in the server's response. It then swaps only the content of that specific frame.
This approach keeps the logic on the server. You aren't managing client-side state in a store; you are simply telling Rails to render a specific piece of HTML. Because the rest of the page remains untouched, the user doesn't lose their place, and the perceived performance increases significantly.
Implementation: A Live Edit Example
Consider a scenario where you want to edit a post title without leaving the index page. This requires a frame that can toggle between a "view" state and an "edit" state.
1. The View Template
Wrap the content in a turbo_frame_tag. In your Rails view (e.g., index.html.erb), use the following:
<%= turbo_frame_tag dom_id(post) do %>
<p><%= post.title %></p>
<%= link_to "Edit", edit_post_path(post) %>
</%= turbo_frame_tag dom_id(post) %>
</%= turbo_frame_tag dom_id(post) %>
2. The Edit Template
The server must return a response containing a frame with the exact same ID. In edit.html.erb:
<%= turbo_frame_tag dom_id(post) do %>
<%= form_with model: post do |f| %%
<%= f.text_field :title %%
<%= f.submit "Save" %%
<% end %%
</%= turbo_frame_tag dom_id(post) %>
3. Execution and Verification
To verify this is working correctly:
- Run the app: Start your Rails server (typically
bin/rails s) and navigate to the post index. - Inspect Network: Open Browser Developer Tools > Network Tab. Click "Edit". You should see a request to the edit path, but the browser will not perform a full page refresh.
- Check Response: Ensure the server response contains the
<turbo-frame id="...">wrapper. If the server returns a full layout (including<body>), Turbo will still extract the frame, but you are wasting bandwidth.
Trade-offs and Limitations
Turbo Frames are powerful, but they introduce two specific challenges: URL synchronization and direct navigation.
The URL Problem
By default, Turbo Frames do not update the browser's address bar. If a user clicks "Edit" inside a frame, the URL remains /posts even though the content is now the edit form. If the user refreshes the page, they will be sent back to the index view, losing their unsaved changes. To fix this, you must add data-turbo-action="advance" to the link, which tells Turbo to update the browser history.
The Direct Access Trap
If a user bookmarks the URL of a frame-targeted action (e.g., /posts/1/edit) and visits it directly, the request is not coming from a Turbo Frame. If your controller only renders the frame, the user will see a nearly blank page with just a small form. You must ensure your controller handles both cases, usually by rendering the full layout when the request is not a Turbo request.
Decision Matrix: Frame vs. Drive
| Feature | Turbo Drive | Turbo Frames |
|---|---|---|
| Scope | Full Page | Partial Page |
| URL Change | Automatic | Manual (via advance) |
| Use Case | Navigating between pages | Inline editing, tabs, filters |
Closing Action
To start optimizing your Rails app, identify the most frequent "small" interactions—such as status toggles, inline edits, or pagination—and wrap them in turbo_frame_tag. Always test the direct URL access to ensure your application remains accessible to users who don't arrive via a frame click.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.