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.
How much faster is it really?
In any case, the benefit can be up to twice as fast in apps with lots of JS and CSS. Of course, your mileage may vary, be dependent on your browser version, the moon cycle, and all other factors affecting performance testing. But at least it's a yardstick.
The best way to find out just how fast it is? Try it on your own application. It hardly takes any effort at all.
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 or Prototype framework, or whatever else have you.
With Turbolinks pages will change without a full reload, so you can't rely on
jQuery.ready() to trigger your code. Instead Turbolinks fires events on
document to provide hooks into the lifecycle of the page.
Load a fresh version of a page from the server:
page:before-changea Turbolinks-enabled link has been clicked (see below for more details)
page:fetchstarting to fetch a new target page
page:receivethe page has been fetched from the server, but not yet parsed
page:changethe page has been parsed and changed to the new version and on DOMContentLoaded
page:updateis triggered whenever page:change is PLUS on jQuery's ajaxSucess, if jQuery is available (otherwise you can manually trigger it when calling XMLHttpRequest in your own code)
page:loadis fired at the end of the loading process.
Handlers bound to the
page:before-change event may return
false, which will cancel the Turbolinks process.
By default, Turbolinks caches 10 of these page loads. It listens to the popstate event and attempts to restore page state from the cache when it's triggered. When
popstate is fired the following process happens:
Restore a cached page from the client-side cache:
page:changepage has changed to the cached page.
page:restoreis fired at the end of restore process.
The number of pages Turbolinks caches can be configured to suit your application's needs:
// View the current cache size Turbolinks.pagesCached(); // Set the cache size Turbolinks.pagesCached(20);
When a page is removed from the cache due to the cache reaching its size limit, the
page:expire event is triggered. Listeners bound to this event can access the cached page object using
event.originalEvent.data. Keys of note for this page cache object include
To implement a client-side spinner, you could listen for
page:fetch to start it and
page:receive to stop it.
document.addEventListener("page:fetch", startSpinner); document.addEventListener("page:receive", stopSpinner);
DOM transformations that are idempotent are best. If you have transformations that are not, hook them to happen only on
page:load instead of
page:change (as that would run them again on the cached pages).
Transition Cache: A Speed Boost
Transition Cache, added in v2.2.0, 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.
The one drawback is that dramatic differences in appearence 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.
Turbolinks will be enabled only if the server has rendered a
Some examples, given a standard RESTful resource:
POST :create=> resource successfully created => redirect to
- Turbolinks ENABLED
POST :create=> resource creation failed => render
- Turbolinks DISABLED
Why not all request types? Some browsers track the request method of each page load, but triggering pushState methods don'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. So links to /images/panda.gif will just work as expected. To whitelist additional file extensions to be processed by Turbolinks, use
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 stuff like
data-confirm from Rails.
page:load event. It may restore functionality of some libraries.
jquery.js but before
//= 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>
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
data-turbolinks-eval attribute to
Triggering a Turbolinks visit manually
You can use
Turbolinks.visit(path) to go to a URL through Turbolinks.
You can also use
redirect_via_turbolinks_to in Rails to perform a redirect via Turbolinks.
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.
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.
gem 'turbolinks'to your Gemfile.
- Restart your server and you're now using turbolinks!
These projects are not affiliated with or endorsed by the Rails Turbolinks team.
- Flask Turbolinks (Python Flask)
- ASP.NET MVC Turbolinks
- PHP Turbolinks Component (Symfony Component)
- PHP Turbolinks Package (Laravel Package)
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!