turbolinks-classic
Classic version of Turbolinks. Now deprecated in favor of Turbolinks 5.
Top Related Projects
Classic version of Turbolinks. Now deprecated in favor of Turbolinks 5.
The speed of a single-page web application without having to write any JavaScript
A full-stack framework for Laravel that takes the pain out of building dynamic UIs.
A rugged, minimal framework for composing JavaScript behavior in your markup.
A collection of composable behaviors for your Stimulus Controllers
Inertia.js lets you quickly build modern single-page React, Vue and Svelte apps using classic server-side routing and controllers.
Quick Overview
Turbolinks Classic is a JavaScript library that speeds up page loads in web applications by replacing the full page refresh with partial updates. It intercepts link clicks and form submissions, fetches the new content via AJAX, and updates the page without a full reload, resulting in faster navigation and a more responsive user experience.
Pros
- Significantly improves perceived page load times
- Easy to implement with minimal changes to existing Rails applications
- Works seamlessly with server-rendered HTML
- Maintains browser history and scroll position
Cons
- Can interfere with JavaScript events and DOM manipulation on page load
- May require additional configuration for complex single-page applications
- Limited to same-origin navigation
- Deprecated in favor of Turbolinks 5 and Turbo
Code Examples
- Basic Turbolinks installation:
// application.js
//= require turbolinks
- Handling Turbolinks events:
document.addEventListener("turbolinks:load", function() {
// Code to run on every page load
console.log("Page loaded with Turbolinks");
});
- Disabling Turbolinks for specific links:
<a href="/some-page" data-turbolinks="false">Link without Turbolinks</a>
Getting Started
To get started with Turbolinks Classic in a Rails application:
-
Add to your Gemfile:
gem 'turbolinks', '~> 2.5.3'
-
Run
bundle install
-
Include Turbolinks in your JavaScript manifest:
// app/assets/javascripts/application.js //= require turbolinks
-
Ensure your layout file has the
data-turbolinks-track
attribute:<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
-
Turbolinks is now active for all internal links in your application.
Competitor Comparisons
Classic version of Turbolinks. Now deprecated in favor of Turbolinks 5.
Pros of turbolinks-classic
- Established and well-documented library
- Seamless integration with Ruby on Rails applications
- Large community support and extensive usage history
Cons of turbolinks-classic
- Older technology, potentially lacking modern features
- May have compatibility issues with newer JavaScript frameworks
- Limited support for single-page application (SPA) functionality
Code Comparison
turbolinks-classic:
// Turbolinks classic initialization
Turbolinks.start()
// Handling Turbolinks events
document.addEventListener("turbolinks:load", function() {
// Your code here
})
Since both repositories refer to the same project (turbolinks-classic), there isn't a distinct code comparison to be made. The code snippet above demonstrates the basic usage of Turbolinks in a web application.
It's worth noting that Turbolinks has evolved since the "classic" version, with newer versions offering improved features and performance. However, the core concept of speeding up page loads by replacing the body of the page without a full page reload remains consistent across versions.
For developers considering Turbolinks, it's recommended to evaluate the latest version of the library and compare it with other modern alternatives to determine the best fit for their specific project requirements.
The speed of a single-page web application without having to write any JavaScript
Pros of Turbo
- More comprehensive framework with additional features like Turbo Frames and Turbo Streams
- Better integration with modern JavaScript frameworks and Single Page Applications
- Improved performance through more efficient DOM updates and caching strategies
Cons of Turbo
- Steeper learning curve due to increased complexity and new concepts
- Potential compatibility issues with older browsers or legacy applications
- May require more extensive refactoring of existing Turbolinks applications
Code Comparison
Turbolinks Classic:
Turbolinks.visit('/new-page')
Turbo:
Turbo.visit('/new-page')
// Or using Frames
<turbo-frame id="my-frame" src="/partial-content"></turbo-frame>
// Using Streams
<turbo-stream action="append" target="messages">
<template>
<div id="message_1">New message</div>
</template>
</turbo-stream>
Turbo offers more advanced features and better integration with modern web development practices, but it may require more effort to implement and maintain compared to the simpler Turbolinks Classic. The choice between the two depends on the specific needs of the project and the development team's familiarity with the technologies.
A full-stack framework for Laravel that takes the pain out of building dynamic UIs.
Pros of Livewire
- Full-stack framework with server-side rendering, reducing JavaScript complexity
- Real-time updates without page reloads, improving user experience
- Seamless integration with Laravel, leveraging existing PHP skills
Cons of Livewire
- Steeper learning curve for developers new to the Laravel ecosystem
- Potentially higher server load due to more frequent server-side processing
- Limited compatibility with non-Laravel projects
Code Comparison
Turbolinks (JavaScript):
document.addEventListener("turbolinks:load", function() {
// Initialize JavaScript for the current page
});
Livewire (PHP):
class SearchComponent extends Component
{
public $query = '';
public function render()
{
return view('livewire.search', [
'results' => $this->query ? User::search($this->query)->get() : []
]);
}
}
Summary
Livewire offers a more comprehensive solution for building dynamic interfaces within the Laravel ecosystem, while Turbolinks provides a simpler approach to improving page load times across various frameworks. Livewire excels in creating reactive components with server-side logic, whereas Turbolinks focuses on enhancing traditional web applications with smoother navigation.
A rugged, minimal framework for composing JavaScript behavior in your markup.
Pros of Alpine
- Lightweight and minimal, with a smaller footprint than Turbolinks
- More flexible, allowing for dynamic UI updates without full page reloads
- Easier to integrate into existing projects without major architectural changes
Cons of Alpine
- Less optimized for full page navigation compared to Turbolinks
- Requires more manual management of state and DOM updates
- May have a steeper learning curve for developers new to reactive frameworks
Code Comparison
Alpine:
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<div x-show="open">Content</div>
</div>
Turbolinks:
<a href="/page" data-turbolinks="true">Navigate</a>
<script>
Turbolinks.visit("/page");
</script>
Alpine focuses on declarative, reactive UI updates within the page, while Turbolinks primarily handles full page navigation and caching. Alpine provides more granular control over UI elements, whereas Turbolinks aims to make traditional server-rendered applications feel more like single-page apps by optimizing page loads.
A collection of composable behaviors for your Stimulus Controllers
Pros of stimulus-use
- Provides a collection of composable behaviors for Stimulus controllers
- Offers more flexibility and customization options for enhancing Stimulus applications
- Actively maintained and regularly updated with new features
Cons of stimulus-use
- Requires Stimulus as a dependency, limiting its use to Stimulus-based projects
- May have a steeper learning curve for developers new to Stimulus ecosystem
- Smaller community and ecosystem compared to Turbolinks
Code Comparison
Turbolinks-classic:
document.addEventListener("turbolinks:load", function() {
// Your code here
});
stimulus-use:
import { useIntersection } from "stimulus-use"
export default class extends Controller {
connect() {
useIntersection(this)
}
}
Summary
While Turbolinks-classic focuses on speeding up page loads by replacing the body of the page, stimulus-use enhances Stimulus controllers with additional behaviors. Turbolinks-classic is more widely adopted and easier to implement, but stimulus-use offers greater flexibility for Stimulus-based applications. The choice between the two depends on the specific needs of the project and the developer's familiarity with the Stimulus ecosystem.
Inertia.js lets you quickly build modern single-page React, Vue and Svelte apps using classic server-side routing and controllers.
Pros of Inertia
- Allows for building modern single-page applications without the complexity of a full SPA framework
- Provides a more seamless integration between backend and frontend, maintaining server-side routing
- Supports multiple frontend frameworks (Vue, React, Svelte) for flexibility in choosing UI libraries
Cons of Inertia
- Requires more setup and configuration compared to Turbolinks' simplicity
- May have a steeper learning curve for developers new to SPA concepts
- Limited to specific backend frameworks (Laravel, Rails) for full feature support
Code Comparison
Turbolinks (JavaScript):
document.addEventListener("turbolinks:load", function() {
// Your JavaScript code here
});
Inertia (Vue.js component):
<template>
<layout>
<h1>{{ title }}</h1>
<p>{{ content }}</p>
</layout>
</template>
<script>
export default {
props: ['title', 'content']
}
</script>
Inertia offers a more structured approach to building interactive applications, while Turbolinks provides a simpler solution for enhancing traditional server-rendered applications. The choice between the two depends on the project's complexity and the development team's preferences.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
Turbolinks Classic is now deprecated
Turbolinks 5 is a ground-up rewrite with a new flow, new events, but the same core idea. It's available at turbolinks/turbolinks. This repository remains available for existing applications built on what we now call Turbolinks Classic.
Turbolinks
Turbolinks makes following links in your web application faster. Instead of letting the browser recompile the JavaScript and CSS between each page change, it keeps the current page instance alive and replaces only the body (or parts of) and the title in the head. Think CGI vs persistent process.
This is similar to pjax, but instead of worrying about what element on the page to replace and tailoring the server-side response to fit, we replace the entire body by default, and let you specify which elements to replace on an opt-in basis. This means that you get the bulk of the speed benefits from pjax (no recompiling of the JavaScript or CSS) without having to tailor the server-side response. It just works.
Do note that this of course means that you'll have a long-running, persistent session with maintained state. That's what's making it so fast. But it also means that you may have to pay additional care not to leak memory or otherwise bloat that long-running state. That should rarely be a problem unless you're doing something really funky, but you do have to be aware of it. Your memory leaking sins will not be swept away automatically by the cleansing page change any more.
No jQuery or any other library
Turbolinks is designed to be as light-weight as possible (so you won't think twice about using it even for mobile stuff). It does not require jQuery or any other library to work. But it works great with the jQuery framework, or whatever else you have.
Events
With Turbolinks pages will change without a full reload, so you can't rely on DOMContentLoaded
or jQuery.ready()
to trigger your code. Instead Turbolinks fires events on document
to provide hooks into the lifecycle of the page.
Event | Argument originalEvent.data | Notes |
---|---|---|
page:before-change | {url} | The page is about to change. Cancellable with event.preventDefault() . Does not fire on history back/forward. |
page:fetch | {url} | A new page is about to be fetched from the server. |
page:receive | {url} | A page has been fetched from the server, but not yet parsed. |
page:before-unload | [affectedNodes] | Nodes are about to be changed. |
page:change | [affectedNodes] | Nodes have changed. Also fires on DOMContentLoaded . |
page:update | Fired alongside both page:change and jQuery's ajaxSuccess (if available). | |
page:load | [newBody] | A new body element has been loaded into the DOM. Does not fire on partial replacement or when a page is restored from cache, so as not to fire twice on the same body. |
page:partial-load | [affectedNodes] | New elements have been loaded into the DOM via partial replacement. |
page:restore | A cached body element has been loaded into the DOM. | |
page:after-remove | affectedNode | An element has been removed from the DOM or body evicted from the cache and must be cleaned up. jQuery event listeners are cleaned up automatically. |
Example: load a fresh version of a page from the server
page:before-change
link clicked orTurbolinks.visit()
called (cancellable)page:fetch
about to send XHRpage:receive
received response from serverpage:before-unload
([currentBody]
) page has been parsed and is about to be changedpage:change
([newBody]
) new body is in placepage:update
page:load
([newBody]
) page has been loaded (progress bar hidden, scroll position updated)page:after-remove
(oldBody
) an old body has been evicted from the cache
Example: partial replacement with Turbolinks.replace()
page:before-unload
([currentNodes...]
) nodes are about to be changedpage:after-remove
(currentNode
) a node has been removed from the DOM and must be cleaned up (fires once per node)page:change
([newNodes...]
) new nodes are in placepage:update
page:partial-load
([newNodes...]
)
Example lifecycle setup:
// using jQuery for simplicity
$(document).on('ready', function(event) {
// initialize persistent state
});
$(document).on('ready page:load', function(event) {
// apply non-idempotent transformations to the body
});
$(document).on('page:partial-load', function(event) {
// apply non-idempotent transformations to the nodes in event.originalEvent.data
});
$(document).on('page:change', function(event) {
// idempotent function
});
$(document).on('page:after-remove', function(event) {
// delete references to the nodes in event.originalEvent.data to prevent memory leaks
});
Page Cache
By default, Turbolinks keeps 10 pages in memory (the full body element is kept in memory, so as not to lose state). On popstate, it attempts to restore pages from the cache. When a page exists in the cache, the following events are triggered:
page:before-unload
([currentBody]
) page is about to be changedpage:change
([cachedBody]
) body from cached page is in placepage:restore
The number of pages Turbolinks caches can be configured to suit your application's needs:
Turbolinks.pagesCached(); // View the current cache size
Turbolinks.pagesCached(20); // Set the cache size
If you need to make dynamic HTML updates in the current page and want it to be cached properly you can call:
Turbolinks.cacheCurrentPage();
Note: performing a partial replacement with URL change will remove the current page from the cache. This is because the replaced nodes cannot be brought back. If the user clicks the back button following a visit with partial replacement, the previous page will be fetched from the server.
Transition Cache: A Speed Boost
Transition Cache makes loading cached pages instantaneous. Once a user has visited a page, returning later to the page results in an instant load.
For example, if Page A is already cached by Turbolinks and you are on Page B, clicking a link to Page A will immediately display the cached copy of Page A. Turbolinks will then fetch Page A from the server and replace the cached page once the new copy is returned.
To enable Transition Cache, include the following in your javascript:
Turbolinks.enableTransitionCache();
The one drawback is that dramatic differences in appearance between a cached copy and new copy may lead to a jarring affect for the end-user. This will be especially true for pages that have many moving parts (expandable sections, sortable tables, infinite scrolling, etc.).
If you find that a page is causing problems, you can have Turbolinks skip displaying the cached copy by adding data-no-transition-cache
to any DOM element on the offending page.
Progress Bar
Because Turbolinks skips the traditional full page reload, browsers won't display their native progress bar when changing pages. To fill this void, Turbolinks offers a JavaScript-and-CSS-based progress bar to display page loading progress (as of v3.0, the progress bar is turned on by default).
To disable (or re-enable) the progress bar, include one of the following in your JavaScript:
Turbolinks.ProgressBar.disable();
Turbolinks.ProgressBar.enable();
The progress bar is implemented on the <html>
element's pseudo :before
element and can be customized by including CSS with higher specificity than the included styles. For example:
html.turbolinks-progress-bar::before {
background-color: red !important;
height: 5px !important;
}
Control the progress bar manually using these methods:
Turbolinks.ProgressBar.start();
Turbolinks.ProgressBar.advanceTo(value); // where value is between 0-100
Turbolinks.ProgressBar.done();
Initialization
Turbolinks will be enabled only if the server has rendered a GET
request.
Why not all request types? Some browsers track the request method of each page load, but triggering pushState
methods doesn't change this value. This could lead to the situation where pressing the browser's reload button on a page that was fetched with Turbolinks would attempt a POST
(or something other than GET
) because the last full page load used that method.
Opting out of Turbolinks
By default, all internal HTML links will be funneled through Turbolinks, but you can opt out by marking links or their parent container with data-no-turbolink
. For example, if you mark a div with data-no-turbolink
, then all links inside of that div will be treated as regular links. If you mark the body, every link on that entire page will be treated as regular links.
<a href="/">Home (via Turbolinks)</a>
<div id="some-div" data-no-turbolink>
<a href="/">Home (without Turbolinks)</a>
</div>
Note that internal links to files containing a file extension other than .html will automatically be opted out of Turbolinks. To whitelist additional file extensions to be processed by Turbolinks, use Turbolinks.allowLinkExtensions()
.
Turbolinks.allowLinkExtensions(); // => ['html']
Turbolinks.allowLinkExtensions('md'); // => ['html', 'md']
Turbolinks.allowLinkExtensions('coffee', 'scss'); // => ['html', 'md', 'coffee', 'scss']
Also, Turbolinks is installed as the last click handler for links. So if you install another handler that calls event.preventDefault()
, Turbolinks will not run. This ensures that you can safely use Turbolinks with things like data-method
, data-remote
, or data-confirm
from Rails.
Note: in v3.0, the default behavior of redirect_to
is to redirect via Turbolinks on XHR + non-GET requests. You can opt-out of this behavior by passing turbolinks: false
to redirect_to
.
By default, Turbolinks includes itself in ActionController::Base
. To opt out of the Turbolinks features in certain controllers (redirect_to
behavior, request_method
cookie, X-XHR-Referer
referrer check, etc.), set config.turbolinks.auto_include
to false
in application.rb
and include Turbolinks::Controller
in the controllers where you use Turbolinks.
jquery.turbolinks
If you have a lot of existing JavaScript that binds elements on jQuery.ready()
, you can pull the jquery.turbolinks library into your project that will trigger ready()
when Turbolinks triggers the page:load
event. It may restore functionality of some libraries.
Add the gem to your project, then add the following line to your JavaScript manifest file, after jquery.js
but before turbolinks.js
:
//= require jquery.turbolinks
Additional details and configuration options can be found in the jquery.turbolinks README.
Asset change detection
You can track certain assets, like application.js
and application.css
, that you want to ensure are always of the latest version inside a Turbolinks session. This is done by marking those asset links with data-turbolinks-track
, like so:
<link href="/assets/application-9bd64a86adb3cd9ab3b16e9dca67a33a.css" rel="stylesheet"
type="text/css" data-turbolinks-track>
If those assets change URLs (embed an md5 stamp to ensure this), the page will do a full reload instead of going through Turbolinks. This ensures that all Turbolinks sessions will always be running off your latest JavaScript and CSS.
When this happens, you'll technically be requesting the same page twice. Once through Turbolinks to detect that the assets changed, and then again when we do a full redirect to that page.
Evaluating script tags
Turbolinks will evaluate any script tags in pages it visits, if those tags do not have a type or if the type is text/javascript
. All other script tags will be ignored.
As a rule of thumb when switching to Turbolinks, move all of your javascript tags inside the head
and then work backwards, only moving javascript code back to the body if absolutely necessary. If you have any script tags in the body you do not want to be re-evaluated then you can set the data-turbolinks-eval
attribute to false
:
<script type="text/javascript" data-turbolinks-eval=false>
console.log("I'm only run once on the initial page load");
</script>
Turbolinks will not re-evaluate script tags on back/forward navigation, unless their data-turbolinks-eval
attribute is set to always
:
<script type="text/javascript" data-turbolinks-eval=always>
console.log("I'm run on every page load, including history back/forward");
</script>
Triggering a Turbolinks visit manually
You can use Turbolinks.visit(path)
to go to a URL through Turbolinks.
You can also use redirect_to path, turbolinks: true
in Rails to perform a redirect via Turbolinks.
Client-side API
Turbolinks
Function | Arguments | Notes |
---|---|---|
visit() | path , options | Load a new page and change the URL. |
replace() | stringOrDocument , options | Replace the current page without changing the URL. |
Option | Type | Notes |
---|---|---|
change | Array | Replace only the nodes with the given ids. |
append | Array | Append the children of nodes with the given ids. |
prepend | Array | Prepend the children of nodes with the given ids. |
keep | Array | Replace the body but keep the nodes with the given ids. |
flush | Boolean | Replace the body, including data-turbolinks-permanent nodes. |
title | Boolean or String | If false , don't update the document title. If a string, set the value as title. |
scroll | Boolean | If false , don't scroll to top (or #target ) after the page is loaded. |
cacheRequest | Boolean | Enable/disable the request cache. |
showProgressBar | Boolean | Show/hide the progress bar during the request. |
Function | Arguments | Notes |
---|---|---|
pagesCached() | None or Number | Get or set the maximum number of pages that should be cached. |
cacheCurrentPage() | ||
enableTransitionCache() | ||
disableRequestCaching() | ||
allowLinkExtensions() | String ... | Whitelist additional file extensions to be processed by Turbolinks. |
Property | Notes |
---|---|
supported | true if the browser fully supports Turbolinks. |
EVENTS | Map of event names. |
Turbolinks.ProgressBar
Function | Arguments | Notes |
---|---|---|
enable() | ||
disable() | ||
start() | ||
advanceTo() | Number | Value must be between 0 and 100. |
done() |
Full speed for pushState browsers, graceful fallback for everything else
Like pjax, this naturally only works with browsers capable of pushState
. But of course we fall back gracefully to full page reloads for browsers that do not support it.
Note: there is currenty no fallback for partial replacement on browsers that don't support pushState
.
Compatibility
Turbolinks is designed to work with any browser that fully supports pushState
and all the related APIs. This includes Safari 6.0+ (but not Safari 5.1.x!), IE10, and latest Chromes and Firefoxes.
Do note that existing JavaScript libraries may not all be compatible with Turbolinks out of the box due to the change in instantiation cycle. You might very well have to modify them to work with Turbolinks's new set of events. For help with this, check out the Turbolinks Compatibility project.
Turbolinks works with Rails 3.2 and newer.
Known issues
- External scripts are not guaranteed to execute in DOM order (#513)
- Iframes in
data-turbolinks-permanent
nodes are reloaded on page load (#511) - Audio and video elements in
data-turbolinks-permanent
nodes are paused on page load (#508) - Partial replacement removes pages from the cache (#551)
Installation
- Add
gem 'turbolinks'
to your Gemfile. - Run
bundle install
. - Add
//= require turbolinks
to your Javascript manifest file (usually found atapp/assets/javascripts/application.js
). If your manifest requires both turbolinks and jQuery, make sure turbolinks is listed after jQuery. - Restart your server and you're now using turbolinks!
Running the tests
Ruby:
rake test:all
BUNDLE_GEMFILE=Gemfile.rails42 bundle
BUNDLE_GEMFILE=Gemfile.rails42 rake test
JavaScript:
bundle install
npm install
script/test # requires phantomjs >= 2.0
script/server # http://localhost:9292/javascript/index.html
Language Ports
These projects are not affiliated with or endorsed by the Rails Turbolinks team.
- Flask Turbolinks (Python Flask)
- Django Turbolinks (Python Django)
- ASP.NET MVC Turbolinks
- PHP Turbolinks Component (Symfony Component)
- PHP Turbolinks Package (Laravel Package)
- Grails Turbolinks (Grails Plugin)
Credits
Thanks to Chris Wanstrath for his original work on Pjax. Thanks to Sam Stephenson and Josh Peek for their additional work on Pjax and Stacker and their help with getting Turbolinks released. Thanks to David Estes and Nick Reed for handling the lion's share of post-release issues and feature requests. And thanks to everyone else who's fixed or reported an issue!
Top Related Projects
Classic version of Turbolinks. Now deprecated in favor of Turbolinks 5.
The speed of a single-page web application without having to write any JavaScript
A full-stack framework for Laravel that takes the pain out of building dynamic UIs.
A rugged, minimal framework for composing JavaScript behavior in your markup.
A collection of composable behaviors for your Stimulus Controllers
Inertia.js lets you quickly build modern single-page React, Vue and Svelte apps using classic server-side routing and controllers.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot