Video

Want to see the full-length video right now for free?

Sign In with GitHub for Free Access

Notes

On this week's episode, Chris is again joined by fellow bot Derek Prior, this time to discuss Turbolinks. Tune in to learn how to speed up your Rails apps, almost automatically!

Turbolinks is a Rails feature, available as a gem and enabled by default in new Rails apps. It is intended to speed up navigating between pages of your application. It works by intercepting all link clicks that would navigate to a page within the app, and instead makes the request via AJAX, replacing the body with the received content. The primary speedup comes from not having to download or parse the CSS & JS files again.

Note, Turbolinks still uses the same server code and requests HTML rather than needing a JSON API or other custom code on the Rails side.

Progressive Enhancement

One nice feature of Turbolinks is that if your app is running in a browser that doesn't support the required features, like pushstate, Turbolinks will gracefully fall back to traditional GET requests and full page loads.

Turbolinks optimization fits best in server rendered applications and should not be used in any app with client side routing. The larger your JavaScript and CSS files, the greater potential speedup you can expect, although it's important to ensure your application's JavaScript code is written in a Turbolinks-safe manner. Read on for details!

Indicating Page Loads

Since Turbolinks prevents normal page navigation when links are clicked, there is a potential UI concern if your page renders take a significant amount of time. The browser will offer no indication that a request has been made, and the page will seem to hang until the server responds.

To combat this, Turbolinks version 2 introduced a progress bar to fill this void. As of Turbolinks version 3 (shipping around the same time as Rails 5), the Turbolinks Progress Bar is enabled by default.

Turbolinks is intended to be largely transparent from an implementation standpoint. Due to this, the Rails core team has chose to enable it by default. Unfortunately there are some subtle edge cases and scenarios in which Turbolinks will not work as expected, and for these reasons we don't think it's a great default.

Currently, Suspenders, our Rails app generator disables Turbolinks. If you're not using Suspenders, you can opt out by using the --skip-turbolinks flag, or by adding to your ~/.railsrc file.

While Turbolinks "Just Works" the vast majority of the time, there are cases where it will fail, and when it does, it can be very subtle and hard to debug. The following are some of the more common failure scenarios, and the ways to avoid them.

Binding to Document Ready

When using jQuery, it is common to use the $(function() {...}) shorthand to bind to the document ready event. Unfortunately, with the default configuration this will not work with Turbolinks. Even worse, it will work on full page loads, but the JS code in the $(function() {...}) will not run on subsequent Turbolinks-powered navigation as Turbolinks does not trigger the document ready event.

Also Bind to Page Load Event

Turbolinks will fire a page:load event when handling navigation, so it is relatively easy to solve the document ready problem by binding to the page:load event, in addition to ready.

Assuming you have the default, roughly as-generated application.js file:

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require_tree .

The following will silently fail on Turbolinks navigation (while working on full page refresh. Maddening!):

$(document).on('ready', function() {
  // your setup code here...
});

This will work under full page refresh & Turbolinks navigation!

$(document).on('ready page:load', function() {
  // your setup code here...
});

While the page:load event binding solution works, it requires constant vigilance to maintain. An alternative solution is to use the jQuery Turbolinks plugin. With jQuery Turbolinks installed as a gem, you only need to update your application.js manifest as follows (note the order):

//= require jquery
//= require jquery.turbolinks
//= require jquery_ujs
//= require turbolinks
//= require_tree .

and then you can write the traditional shorthand document ready code, and the plugin will automatically wire it up to Turbolinks page:load event:

$(function() {
  // This code will run on either full page refresh, or Turbolinks navigation
  // thanks to the jQuery Turbolinks plugin
});

Make Sure To Put Your JS In The Head

Turbolinks speedup is largely due to the fact that it ignores the <head> and included files like your application.js and application.css on Turbolinks page navigations. As such, it is critical that you put these files, particularly the application.js, in the <head> of the document to take advantage of the Turbolinks magic. If your application.js is referenced at the bottom of the <body> then the browser will still fetch and parse it on all navigation as it is part of the content Turbolinks inserts into the body.

Using Async True

While Turbolinks requires you to put your application.js in the <head>, this goes against the common recommendation to put your JS at the bottom of the body to allow the page to render before the javascript has been downloaded and parsed. Luckily, by using the async attribute on our <srcript> tag, we can get the best of both worlds:

<%= javascript_include_tag "application", async: true %>

By adding async: true, we instruct the browser to fetch this resource asynchronously and not block page load.

Analytics Gotcha

With Turbolinks, any sort of page tracking analytics code must go in the body of the page, rather than the <head>. You can load Google Analytics or similar as a library in the head, but any page related tracking must go in the body as this is all that will run with Turbolinks enabled.

Turbolinks 3 is currently in development but should be released within the next few months. One interesting aspect of Turbolinks 3 is that rather than strictly doing the full <body> replacement that Turbolinks 1 and 2 do, Turbolinks 3 has support for partial dom replacement. While this seems nice and is a bit closer to PJAX, the spiritual ancestor of Turbolinks introduced by Github, it nonetheless adds a good bit more complexity to the whole Turbolinks dance. With partial replacement, there is the possibility of involving the server and Rails controllers in deciding what content to replace, and overall things seem a bit more complex than with earlier Turbolinks versions.

Overall we think Turbolinks is worth a look, especially if you have a relatively straightforward server-rendered app, and all the more so if you have a significant amount of JS and CSS code. Derek and Sean recently discussed Turbolinks and their experiences with it in detail on the Bikeshed podcast episode 26, so check that out if you'd like to go even deeper on Turbolinks.