Introducing Turbolinks for Rails 4.0

Introducing Turbolinks for Rails 4.0

David Heinemeier Hansson, creator of Ruby on Rails, recently announced on Twitter that a feature called Turbolinks will ship as default-on in the Gemfile of Ruby on Rails 4.0. Turbolinks is already powering the mobile website of Basecamp.

Someone at Hacker News posted a link to the Turbolinks repository on Github and titled it:

Turbolinks for Rails (like pjax)

So what is PJAX and what does Turbolinks do differently?

JavaScript pushState

HTML5 introduced several new APIs to JavaScript, one being the History interface. pushState allows JavaScript to store arbitrary data into the session history, combined with a title and an optional URL. The back() and forward() methods then allow you to navigate through the pushed session history. This way pushState can be used to store the navigation history of the current page and dynamically switch backwards on forwards through different states without reloading the whole page.

PJAX

PJAX is a portmanteau of the terms pushState and AJAX. The idea behind PJAX is simple: Instead of reloading the whole page when clicking a link, just load the part of the page that needs updating. This obviously requires JavaScript on the client side to send an AJAX request to the server, fetch the data and replace the component of the page that needs to be updated.

The above sketch shows how PJAX works in practice. First (1) a normal GET request is sent to the server, which returns a complete page (2). All consecutive requests (3) triggered by clicking on a link will only fetch part of a page (4).

On the client-side you define the container (#main in this case) that should be replaced when a link to /authors is clicked.

$.pjax({
  url: '/authors',
  container: '#main'
})

PJAX also requires the server-side application to only return the selected part of the page. For this reason PJAX adds a HTTP header called X-PJAX to the request, to let the web-application know that the request is coming from PJAX. In Rails you can simply check for that header and set render :layout => false.

if request.headers['X-PJAX']
    render :layout => false
end

There are other ways of using PJAX in Rails, most of which have been described by Ryan Bates in Railscasts Episode #294. PJAX has its downsides in that you carefully have to think about which parts of a page to replace.

Turbolinks

Turbolinks also uses pushState, but instead of replacing only parts a page it loads a complete website from the server and replaces the <title> and <body> in the currently loaded DOM. By default it applies this to all links on a page. So unlike PJAX you don't have to mark links and containers to support in place reloading, Turbolinks will handle that for you.

Right now, Turbolinks is in a very early stage of development, but as mentioned before, already in use at Basecamp. To use it in your Ruby (Sinatra, Rails, ..) project simply add the turbolinks gem to you app:

gem 'turbolinks'

Do not forget to run bundle install after editing the Gemfile to install the gem. You will then need to add the turbolinks.js.coffee file to the asset pipeline by adding a line in app/assets/javascripts/application.js:

//= require turbolinks

From now on all requests made by a browser that supports pushState will be handled by turbolinks. Browsers that have no support for pushState will still work, as they just fallback to the default link action. You can check if everything is working by opening the developer tools in your browser (hit F12 in Chrome) and opening the Network tab. You should see that turbolink.js is handling the requests when you click on a link.

If you want to manually exclude links from being handled by Turbolinks you can add the data-no-turbolink attribute to the link tag:

<a href="/articles" data-no-turbolink>Articles</a>

This will tell Turbolinks to ignore this link. You can apply this attribute to any container up to <body> and Turbolinks will ignore all links inside that container. One problem arises when you use document.ready in JavaScript, that event only is fired when the DOM has finished loading, but will not be triggered when Turbolinks performs a page change. A quick-fix for this would be to extract all code tied to document.ready into a seperate function and bind it to both document.ready and the page:change event:

$(function() {
   initPage();
});
$(window).bind('page:change', function() {
  initPage();
})

Note: Turbolinks is written in CoffeeScript and will therefore require the coffeescript-rails gem in order to be compiled to javascript. Make sure that this gem is added in your application. If you do not want to use CoffeeScript in your application you can manually download and compile the turbolinks.js.coffee to JavaScript. Have a look at the CoffeeScript documentation on how to install CoffeeScript and then compile the file by executing coffee -c turbolinks.js.coffee which will compile the file to turbolinks.js.

Conclusion

The user JuDue asked an interesting question on Hacker News:

[..] the inevitable vs PJAX conversation (ie: is it flexible enough? Does it need to be?)

As so often the answer is: It depends. Turbolinks definitely improved client-side page loading. The problem is, that the server still has to render the complete website. If that is a bottleneck in your application then Turbolinks will not help. PJAX on the other side can certainlysolve this by rendering only those parts of a website that really need to be updated, but at the cost of additional work when developing the application.

As for flexibility, it highly depends on your needs. If you only have a few links where you want to exclude Turbolinks then it should be no problem. If you serve large websites with a lot of page content you might want to stick to PJAX to update only small parts of your page and keep the network traffic to a minimum.

To wrap things up: Turbolinks will improve your page load significantly if your pages share JavaScript and CSS styling. PJAX comes in handy, when server-side performance is an issue.

Other Resources