Turbo Drive: Zero-Config Speed for Your Entire Site
You just installed Turbo. Congratulations—your entire site is now faster. You didn't change any HTML. You didn't refactor your controllers. You just imported a library, and suddenly every link click feels instant.
That's Turbo Drive. It's the foundation of everything else in Turbo, and it works automatically with zero configuration. Let me show you what's actually happening.
What Turbo Drive Does
Turbo Drive intercepts every link click and form submission on your page. Instead of letting the browser do a full page reload, it fetches the new page via AJAX and swaps out the body content.
Here's what normally happens when you click a link:
1. Browser throws away current page 2. Browser requests new page 3. White flash while waiting 4. Browser downloads HTML, CSS, JS, images—everything 5. Browser parses and renders from scratch
Here's what happens with Turbo Drive:
1. Turbo intercepts the click 2. Turbo fetches new page via fetch() 3. Progress bar shows at top 4. Turbo swaps the <body> content 5. URL updates, done
No white flash. No re-downloading CSS and JavaScript. No re-parsing your entire asset bundle. The header, footer, and all your scripts stay loaded. Only the content changes.
Navigation that used to take 2-3 seconds now takes 200-300ms. And you didn't write a single line of JavaScript to make it happen.
Installation
If you're using Rails 7+, you already have it. Turbo is included by default.
For everyone else, check the official installation guide:
npm install @hotwired/turbo
Then in your JavaScript:
import "@hotwired/turbo"
That's it. Just importing Turbo activates it. Every link and form on your site now uses Turbo Drive.
If you need a CDN, use jsDelivr:
<script type="module" src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@latest/dist/turbo.es2017-esm.min.js"></script>
Your server doesn't change at all. It still renders complete HTML pages. Turbo just makes the delivery faster.
The Progress Bar
You'll notice a thin progress bar at the top of the page during navigation. It appears after 500ms by default—long enough that fast pages don't show it, short enough that slow pages get visual feedback.
Users tolerate longer waits when they see progress. A 3-second load with a progress bar feels faster than a 1-second load with a white screen.
Customize the delay:
// Show progress bar after 200ms Turbo.config.drive.progressBarDelay = 200
Style it with CSS:
.turbo-progress-bar {
height: 4px;
background-color: #0076ff;
}
Instant Back/Forward
Hit the back button and the previous page appears instantly. No network request at all.
Turbo caches a snapshot of every page you visit. When you navigate back, it shows the cached version immediately while fetching a fresh copy in the background. If the page changed, it updates silently.
This makes browsing feel like a native app. Back and forward buttons are instantaneous.
Clear the cache programmatically when server state changes:
Turbo.cache.clear()
Control caching per-page with meta tags:
<!-- Don't cache this page --> <meta name="turbo-cache-control" content="no-cache"> <!-- Cache it, but don't show preview while navigating --> <meta name="turbo-cache-control" content="no-preview">
The
no-preview option is useful for pages with flash messages. You don't want "Post created!" showing up again when someone hits the back button.Link Prefetching
Turbo 8 added automatic link prefetching. When you hover over a link, Turbo starts fetching it before you click. By the time your click registers, the page is often already loaded.
This shaves 100-300ms off every navigation. It's enabled by default.
Disable it globally:
<meta name="turbo-prefetch" content="false">
Or per-link for expensive pages:
<a href="/expensive-page" data-turbo-prefetch="false"> Don't prefetch this </a>
When to Disable Turbo Drive
Turbo Drive works great by default, but sometimes you need to opt out.
<!-- File download --> <a href="/report.pdf" data-turbo="false">Download PDF</a> <!-- External link --> <a href="https://stripe.com" data-turbo="false">Pay Now</a> <!-- OAuth redirect --> <a href="/auth/google" data-turbo="false">Sign in with Google</a>
Use
data-turbo="false" for file downloads, external links, OAuth flows, and payment gateways. Anything that needs a real browser navigation.Disable for an entire section:
<div data-turbo="false"> <!-- Everything here uses normal navigation --> </div>
Forms Work Too
Turbo Drive handles forms the same way it handles links. GET forms update the URL and fetch results. POST forms submit, follow the redirect, and render the new page.
<form action="/search" method="get"> <input name="q" type="search"> <button>Search</button> </form>
Submit that form and Turbo updates the URL to
/search?q=query, fetches the results, and swaps them in. History entry added. Back button works.For POST forms, your server should redirect after success—the standard Post/Redirect/Get pattern. Turbo follows the redirect automatically.
# Rails example
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post
else
render :new, status: :unprocessable_entity
end
end
That
status: :unprocessable_entity (422) tells Turbo to render the response in place, showing validation errors. Standard Rails pattern, works perfectly with Turbo.Permanent Elements
Some things shouldn't reset during navigation. Audio players. Chat widgets. Live notifications.
Mark them with
data-turbo-permanent:<div id="audio-player" data-turbo-permanent> <audio src="/stream.mp3" autoplay></audio> </div>
Permanent elements keep their state across navigations. The audio keeps playing. Event listeners stay attached. Just make sure the element has an
id and exists on both pages.The JavaScript Gotcha
This is the one thing that trips people up. If you have JavaScript that runs on page load:
// This only runs on first page load
document.addEventListener('DOMContentLoaded', () => {
initializeWidgets()
})
That won't work after Turbo navigation.
DOMContentLoaded only fires once because Turbo doesn't reload the page.Use
turbo:load instead:// Runs on first load AND every Turbo navigation
document.addEventListener('turbo:load', () => {
initializeWidgets()
})
Or better yet, use Stimulus. But that's another post.
Common Issues
Flash messages persist on back button: Use
no-preview cache control, or mark flash elements with data-turbo-temporary so they're removed before caching.Third-party scripts break: Reinitialize them on
turbo:load, or wrap them in data-turbo="false" sections.Analytics not tracking: Fire pageview events on
turbo:load:document.addEventListener('turbo:load', () => {
gtag('event', 'page_view', {
page_path: location.pathname
})
})
The Bottom Line
Turbo Drive is the simplest performance win you'll ever get. Include the library, and every navigation on your site is automatically faster. No refactoring. No new architecture. No build pipeline.
Three things to remember:
1. Include Turbo—navigation is automatically accelerated
2. Use
turbo:load—not DOMContentLoaded3. Use
data-turbo="false"—for downloads, external links, OAuthThat's it. Your whole site now feels like a single-page app.
But Turbo Drive updates the entire body on every navigation. What if you only want to update part of the page—a sidebar, a comment section, a modal? That's where Turbo Frames come in. Next post.