Introduction
Turbo Drive intercepts every link click and form submission, fetches the response in the background, and swaps the page content without a full browser reload. The result is SPA-like speed with zero JavaScript from you.
Key Concepts
- Page Visit: Turbo intercepts a link click and fetches the destination via
fetch. - Body Swap: Turbo replaces the
<body>from the response, preserving<head>when possible. - Preview Cache: Turbo caches page snapshots for instant back-button previews.
- Progress Bar: A thin bar shown when server responses take over 500ms.
Real World Context
Without Turbo Drive, every link click causes the browser to tear down the page, request a new one, re-parse CSS/JS, and rebuild the DOM. Turbo Drive eliminates this overhead, saving 100-300ms per navigation in typical Rails apps.
Deep Dive
How Navigation Works
1. User clicks <a href="/products">
2. Turbo intercepts the click
3. Turbo sends fetch() to /products
4. Server renders full HTML as usual
5. Turbo extracts <body> from response
6. Turbo merges <head>, replaces <body>
7. Turbo updates URL and history
8. turbo:load event fires
The server doesn't know Turbo is involved. It renders a complete HTML page as always.
It Works Automatically
All standard Rails links work with Turbo Drive:
erb<%= link_to "Products", products_path %> <%= link_to "Edit", edit_product_path(@product) %> <%= button_to "Delete", product_path(@product), method: :delete %>
No special attributes needed. Turbo intercepts all links within your domain by default.
Opting Out
Use data-turbo="false" when you need a full page reload:
erb<%= link_to "Download PDF", report_path(format: :pdf), data: { turbo: false } %> <div data-turbo="false"> <!-- All links here bypass Turbo --> </div>
The attribute is inherited by descendants.
Important Events
javascript// Fires on every Turbo navigation (replaces DOMContentLoaded) document.addEventListener("turbo:load", () => { // Initialize third-party libraries here }) // Clean up before Turbo caches the page document.addEventListener("turbo:before-cache", () => { // Remove tooltips, modals, temporary UI })
turbo:load fires on every navigation, unlike DOMContentLoaded which fires only once. Use it for any initialization code, or better yet, use Stimulus controllers.
Common Pitfalls
- Using DOMContentLoaded — Only fires once. After Turbo navigations it won't fire again. Use
turbo:loador Stimulus. - Not handling preview cache — Back-button shows cached snapshot. Open modals/tooltips appear in the preview. Clean up with
turbo:before-cache.
Best Practices
- Use Stimulus instead of event listeners — Controllers auto-connect/disconnect with the DOM.
- Set cache control for dynamic pages — Use
<meta name="turbo-cache-control" content="no-cache">for frequently changing pages.
Summary
- Turbo Drive intercepts links and forms, fetching pages via fetch and swapping
<body>. - Works automatically with all standard Rails links and forms.
- Use
data-turbo="false"to opt out specific elements. turbo:loadreplacesDOMContentLoadedfor code that runs on every page.- Turbo caches pages for instant back-button previews.
Code Examples
# Controllers need no changes for Turbo Drive
class ProductsController < ApplicationController
def index
@products = Product.all
# Renders full HTML — Turbo extracts <body> client-side
end
def show
@product = Product.find(params[:id])
# Server has no idea Turbo is involved
end
end