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.

PJAX data flow diagram Data flow when using PJAX

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'
})
Defining a PJAX action

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
Deactivate the layout for PJAX requests

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'
Add turbolinks to your Gemfile

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
Require turbolinks as part of the asset pipeline

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.

Developer console in Chrome showing Turbolinks Developer console in Chrome showing Turbolinks

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>
Manually exclude a link from Turbolinks

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();
})
Execute code when page is changed or document ready

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

Comments


Avatar
David James – over 1 year ago

Thanks for the writeup. A small clarification. You write, "Turbolinks ... replaces the <head>..." That isn't fully accurate. From a quick code read of fetchReplacement in turbolinks.js, the head is not being replaced. This is a good thing, b/c if you really did reload the head, that would trigger reloading of the links too. The title is being handled correctly, BTW.

Avatar
Fabian Becker – over 1 year ago

Thanks for your comment David. You are indeed correct, in that only the title and body get replaced. I clarified this in the text.

Avatar
– over 1 year ago

I really hope they don't ship this with default on. People new to Rails will be put off by having to immediately deal with Ajax instead of integrating it as needed. I don't like that I have to add a data attribute to turn it off and fuddle up the markup more. Data attribs are great but they are being overused for turning off defaulted behaviors from integration vendors. I feel they indicate extended behavior and not for shutting off some framework dependency. Maybe I'm just used to getting my app to work without Ajax first, and adding it where it makes the most sense. Some customers don't need to ajax the mofo, and its crazy to make it the default on.

Avatar
Nico Ritsche – over 1 year ago

If you are interested in an ajaxify solution in form of a gem that is a bit more flexible and also has a fallback for browsers without pushState: https://github.com/ncri/ajaxify_rails

Avatar
– 10 months ago

Great article ! As being novice in ruby in rails I got my first answer of $(document).ready() method. Thanks a tone !

Avatar
Jason Parraga – 9 months ago

The page:change function was a life saver. Thanks alot, I was having alot of trouble having window events getting detected.

Avatar
Nikita Singh – 7 months ago

Thanks, really helpful !

First sign in through Github or Twitter