Using Rails 7 Hotwire Turbo to Keep Page Updates Lightweight
Learn how Rails 7’s Hotwire Turbo Drive and Turbo Frames let you replace full‑page reloads with swift, server‑rendered updates – demonstrated with a comment form that needs no custom JavaScript.
20 Aug 2025, 19:57 UTC

Problem: Full‑page reloads feel slow
In a classic Rails app every link or form submission triggers a full HTML round‑trip. The browser discards the current page, rebuilds the DOM, and the user sees a blank flash before the new content appears. For simple interactions like adding a comment this overhead hurts perceived performance and often pushes teams toward heavyweight JavaScript frameworks just to regain responsiveness.
Thesis: Turbo Drive and Turbo Frames give SPA‑like updates while staying on the server
Hotwire’s Turbo library intercepts normal navigation and form posts, runs them as XHR requests, and swaps only the needed parts of the page. The server continues to render full HTML templates, so you keep the familiar Rails view layer but avoid the full‑page reload penalty.
How Turbo Drive works
When a user clicks a link or submits a form, Turbo Drive prevents the default browser navigation, sends an XHR with the same URL and HTTP method, and expects an HTML response. It then replaces the <body> element (or a targeted frame) and updates the browser URL via the History API. From the user’s perspective the page updates instantly without a white flash.
Using Turbo Frames for scoped updates
Turbo Frame lets you limit the swap to a specific DOM element. You wrap the target area in a <turbo-frame id="…"> tag and annotate the triggering element (usually a form) with data: { turbo_frame: "frame-id" }. On submission Turbo looks for a matching frame in the response and replaces only that frame’s contents.
Worked example: Adding a comment without custom JavaScript
Assume a basic Comment model with a body text field. We’ll make the comment list and the new‑comment form update via Turbo.
- Generate the scaffold (Rails 7 includes turbo-rails by default):
rails g scaffold Comment body:text rails db:migrate - Wrap the comment list in a frame (
app/views/comments/index.html.erb):<h1>Comments</h1> <turbo-frame id="comment_list"> <%= render @comments %> </turbo-frame> <%= render "form" %> - Annotate the form to target the same frame (
app/views/comments/_form.html.erb):<%= form_with model: comment, local: true, data: { turbo_frame: "comment_list" } %> <div class="field"> <%= label :body %> <%= text_area :body, rows: 3 %> </div> <div class="actions"> <%= submit_tag %> </div> <% end %> - In the controller, respond with HTML (the default) – Turbo will replace the frame with the rendered template:
def create @comment = Comment.new(comment_params) respond_to do |format| if @comment.save format.html { redirect_to comments_path, notice: "Comment was successfully created." } format.turbo_stream # renders create.turbo_stream.erb if you want a stream else format.html { render :new, status: :unprocessable_entity } end end end
When the user submits the form:
- Turbo Drive intercepts the POST and sends an XHR to
/comments. - The
createaction saves the comment and redirects to#index(HTML). - Turbo receives the HTML response, finds the
<turbo-frame id="comment_list">inside, and swaps the existing frame with the new one that now contains the freshly added comment. - The browser URL updates to
/commentsbut there is no full‑page reload.
All of this happens with zero lines of custom JavaScript; the only JavaScript involved is the Turbo library that ships with Rails 7.
Trade‑off and limitation
Because Turbo relies on server‑rendered HTML fragments, it works best for updates that can be expressed as replacement markup. Highly interactive client state – think drag‑and‑drop lists, canvas drawing, or complex form wizards – still needs traditional JavaScript or a Stimulus controller. Additionally, browsers with JavaScript disabled will fall back to the classic full‑page request; if you need to support those users you must ensure your non‑Turbo paths remain functional.
Actionable closing
Start small: pick a page that suffers from repetitive full reloads (e.g., a paginated index or a nested form). Wrap the changing region in a <turbo-frame>, add data: { turbo_frame: "your-frame-id" } to the triggering link or form, and verify in the browser’s Network tab that only one XHR occurs and the URL updates without a blank flash. If the update behaves as expected, you’ve gained SPA‑like speed while keeping your Rails view logic intact.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.