Hotwire - Turbo Drive Intro
12 Apr 2025Turbo Drive accelerates links and form submissions by negating the need for full page reloads.
What does this mean?
In vague terms, Turbo Drive:
- monitors all
<a href>
s and form submissions to the host domain - when detected, it prevents the Browser from performing it’s usual action (following href or form action) and instead:
- performs the action in the background with JavaScript
- updating the browsers current URL with the History API
- requesting the next page using the Fetch API
- form submissions will follow the redirect
- rendering the response
- when rendering, Turbo Drive:
- replaces the contents of
<body>
and merge the contents of<head>
- this preserves the JS
window
, DOM objects,<html>
across page navigations
- replaces the contents of
- performs the action in the background with JavaScript
Page Navigation
Page navigation is a visit to a location (URL) with an action (one of the following: advance, replace, or restore).
A visit contains everything that happens from the “click” to the render of the response. Things like:
- changing browser history
- issuing network request
- restoring a copy of the page from cache
- rendering the final response
- updating the scroll position
The point of merging instead of replacing the
<head>
elements is that if<title>
or<meta>
tags change, say, they will be updated as expected, but if links to assets are the same, they won’t be touched and therefore the browser won’t process them again
Application Visits
- have an action of advance or replace
- this determines how the browser history is updated
- advance is the default action and pushes a new entry onto the browser history stack.
- in native apps, the advance action looks like a new Activity view popping onto the previous view
- replace does what it sounds like, it replaces the current history entry with the next location. that history location is discarded. need the
data-turbo-action="replace"
attribute orTurbo.visit("/edit", { action: "replace" })
.
- initiated by clicking a Turbo Drive link or by executing
Turbo.visit(url)
- always performs a network request and renders the response
If possible, Turbo Drive will render a preview of the page from cache immediately after the visit starts. This improves the perceived speed of frequent navigation between the same pages.
- If the location of the visit has an anchor, Turbo Drive will attempt to scroll to it
Restoration Visits
This type of visit is automatically performed when you navigate with the “Back” or “Forward” buttons in a Browser or the equivalent iOS and Android apps.
If possible, Turbo Drive will render a copy of the page from cache without making a request. Otherwise, it will retrieve a fresh copy of the page over the network.
The scroll position of each page is saved before navigating away from a page and is restored during a restoration visit.
Do not attempt to manually make a restoration visit.
Performing a visit with a different method
Links by default submit GET requests to the server. You can change that with the data-turbo-method
attribute:
<a href="/articles/54" data-turbo-method="delete">Delete the article</a>
You should consider that for accessibility reasons, it’s better to use actual forms and buttons for anything that’s not a GET. — Performing a visit with a different method
TODO: I want a source to backup this claim. I know that GET
requests are “safe” and should not modify a resource so maybe that also implies that anchor elements should never modify resources? If so, then does that mean I shouldn’t use anchor elements for delete requests? That would mean I need to use a form to make delete requests and I don’t think I’ve seen a form setup to perform a delete request in the wild before.
UPDATE
Here’s one short mention
- https://railsdesigner.com/link-button-to/
This mentions that
link_to method: :post
actually creates a hidden form when clicked - https://mixandgo.com/learn/ruby-on-rails/link-to
This mentions that
link_to data: { turbo_method: :delete }
creates a hidden form as well - https://hotwire.io/documentation/turbo/handbook/drive
The source code for
link_to
always creates an anchor element, so is the “form” something that happens client-side? Did the behavior change? or are the previous two articles wrong? - https://apidock.com/rails/ActionView/Helpers/UrlHelper/link_to/
Aha!
When you do a link_to “Delete”, @some_obj, :method => “delete”, :confirm => “Are you sure?” Rails 3 will generate Delete rails.js will creates observers that convert that into a form. Be aware that this probably won’t work as a link from inside a form (since forms in forms isn’t valid).
- https://apidock.com/rails/ActionView/Helpers/UrlHelper/link_to#1077–method-delete-etc- so this is probably still true and it happens client-side. I assume the reason why articles still say to use actual forms is because it’s more semantic, doesn’t rely as heavily on JS, and is better for SEO and accessibility.
Confirmation
You can make a link/button require confirmation before performing the visit.
Add:
data-turbo-method
data-turbo-confirm
<a href="/articles" data-turbo-method="get" data-turbo-confirm="Do you want to leave this page?">Back to articles</a>
<a href="/articles/54" data-turbo-method="delete" data-turbo-confirm="Are you sure you want to delete the article?">Delete the article</a>
The default behavior is to execute the browser’s confirm
function, but you can customize the behavior with Turbo.config.forms.confirm = confirmMethod
.
Turn Turbo off on specific links or forms
<a href="/" data-turbo="false">Disabled</a>
<form action="/messages" method="post" data-turbo="false">
<!-- … -->
</form>
This attribute transfers to children elements.
<div data-turbo="false">
<a href="/">Disabled</a>
<form action="/messages" method="post">
<!-- … -->
</form>
</div>
both the anchor and form have turbo disabled.
<div data-turbo="false">
<a href="/" data-turbo="true">Enabled</a>
</div>
Inversly, if Turbo Drive is globally off
import { Turbo } from "@hotwired/turbo-rails"
Turbo.session.drive = false
you can turn it on for specific links like this
<a href="/" data-turbo="true" data-turbo-stream="true">Enabled</a>
Form Submissions
Turbo Drive handles form submissions in a manner similar to link clicks. The key difference is that form submissions can issue stateful requests using the HTTP POST method, while link clicks only ever issue stateless HTTP GET requests. — Form Submissions
TODO This is confusing because the article has several examples of anchor elements with data-turbo-method="delete"
. Do they mean outside of Turbo Drive, form submissions use POST and links always use GET? That seems irrelavent to mention when comparing how things are handled with Turbo Drive. It’ would be clearer to say that “because forms submissions are stateful, Turbo Drive dispatches events at different points of the submission.
Events:
turbo:submit-start
turbo:before-fetch-request
turbo:before-fetch-response
turbo:submit-end
These events are emitted from the <form>
and bubble up through the document.
You can use these events to customize the appearance of the form when a submission occurs.
Redirecting
Turbo Drive expects a 303
redirect response code and will follow and use to update the page without a full reload.
Turbo Drive doesn’t render from 2xx
response codes after a POST is because browser’s have built in behavior that Turbo can’t replicate or improve.
When the response code is 4xx
or 5xx
, validation error messages are displayed.
If the form submission is a GET request, you may render the directly rendered response by giving the form a data-turbo-frame target. If you’d like the URL to update as part of the rendering also pass a data-turbo-action attribute.
Streaming
Servers may also respond to form submissions with a Turbo Streams message by sending the header Content-Type: text/vnd.turbo-stream.html followed by one or more
<turbo-stream>
elements in the response body. This lets you update multiple parts of the page without navigating
- https://stackoverflow.com/a/56505707
- https://usability.yale.edu/web-accessibility/articles/links
- https://www.a11yproject.com/posts/creating-valid-and-accessible-links/#when-should-you-use-a-button-instead%3F
- https://adrianroselli.com/2016/01/links-buttons-submits-and-divs-oh-hell.html