Eliminating Full-Page Reloads with Turbo Frames
Learn how to use Turbo Frames to isolate page updates and eliminate full-page reloads in server-side rendered applications, maintaining a fluid user experience without complex JS.
16 Jan 2026, 15:30 UTC

The Friction of the Full-Page Refresh
In a traditional server-side rendered application, every user interaction—clicking a "Edit" button, submitting a filter form, or toggling a status—triggers a full browser reload. This disrupts the user's scroll position, resets local UI state, and creates a perceptible lag that makes a web app feel like a collection of static documents rather than a fluid tool.
The goal is to update a specific piece of the page without touching the rest of the DOM. While this used to require complex JavaScript frameworks and JSON APIs, Turbo Frames (part of the Hotwire suite) allow you to achieve this by treating the server as the source of truth for HTML fragments.
How Turbo Frames Isolate Page State
A Turbo Frame is essentially a named container in your HTML. When a link or form inside that frame is clicked, Turbo intercepts the request. Instead of letting the browser navigate to a new URL, Turbo fetches the page and looks for a frame with the same ID in the response. It then swaps only that specific section of the DOM.
This means the rest of your page—your navigation bar, sidebars, and background music players—remains untouched. The server still handles the logic and rendering, but the browser only updates the fragment that changed.
Practical Implementation: An Inline Editor
Consider a scenario where a user wants to edit a record (like a Task name) without leaving the index list. In a standard Rails application (version 7+), you can wrap the record in a frame.
The Index View
<!-- app/views/tasks/index.html.erb -->
<turbo-frame id="task_123">
<p>Task: Finish Technical Blog</p>
<= link_to "Edit", edit_task_path(task), data: { turbo_frame: "task_123" } =>
</turbo-frame>
The Edit View
The server must respond with a frame that has the exact same ID. If the IDs do not match, Turbo will default to a full-page reload.
<!-- app/views/tasks/edit.html.erb -->
<turbo-frame id="task_123">
<= form_with model: @task do |f| =>
<= f.text_field :name =>
<= f.submit "Save" =>
</form_with>
</turbo-frame>
Verification Steps
- Network Inspection: Open your browser's Developer Tools (Network tab). Click the "Edit" link. You should see a request that returns an HTML response containing the
<turbo-frame id="task_123">element, but the browser window should not flash or reload. - DOM Check: Inspect the element to ensure the
<p>tag was replaced by the<form>tag without affecting the surrounding page elements. - Fallback Test: Temporarily change the ID in the
edit.html.erbfile to"task_wrong". Clicking the link should now trigger a full-page refresh, confirming that Turbo relies on the ID match.
Trade-offs and Constraints
While Turbo Frames simplify the UI, they introduce specific architectural constraints:
- URL Synchronization: By default, updating a frame does not change the URL in the browser's address bar. If you want the user to be able to bookmark the "Edit" state, you must add
data-turbo-action="advance"to the link. - Nested Frame Complexity: Placing frames inside other frames can create "request chains" that are difficult to trace. If a nested frame triggers a request, it only updates itself, which may leave the parent frame in an inconsistent state.
- The ID Dependency: Because the swap depends on matching IDs, dynamic content requires strict naming conventions (e.g.,
task_#{task.id}) to avoid updating the wrong part of the page.
Closing Action
To start reducing page reloads, identify the smallest interactive component on your page that changes state independently. Wrap it in a <turbo-frame>, ensure your controller returns a view with a matching ID, and verify the behavior via the Network tab. Start with simple "Edit/Save" toggles before moving to more complex asynchronous filters.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.