Turbolinks: SPA-like Experience Without The SPA-framework Hassle

Ronney Bezerra
Goiabada
Published in
8 min readDec 11, 2017

--

Single-page applications (SPAs) are everywhere. I believe at this point most developers have played with them, or at least have a rough guess of what they try to solve. The concept became very popular in the past few years and has been applied in both small and considerably big (Facebook, Twitter, Netflix) applications.

The idea is to load a full page from the server just once. Once the first page is loaded, the application responds to the user interaction by dynamically updating the content of the current page without requiring entire new pages from the server. As a result, you have a web application that behaves as a typical desktop app. Only the necessary content will change and the users don't need to wait for a full page reload every time they click on a link or submit a form.

Before you decide whether or not you should go for the SPA approach, you need to understand the positive and negative aspects of it. You can find some more detailed coverage for that here, while our post will focus on the following pros and cons:

Pros

  • Faster responses: Regardless of the browser cache, the application doesn't reload all of the resources like CSS and JavaScript files, images and fonts every time a new request is made. It will be done just once, then only business data will be passed back and forth. Basically, less content to load leads to faster user interaction feedback.
  • Better user experience: SPAs are focused on reducing UI response delays. That allows users to perform more actions in less time, resulting in a more seamless experience.

Cons

  • First page load: As said before, most of the resources are loaded in the first page, which can give an initial impression of slowness when compared to a more traditional web page. Also, the content is usually loaded asynchronously, which can result in some unexpected behavior.
  • Added complexity on the client: To develop a SPA you will probably need a client-side framework, adding complexity to your project. Also, instead of one full-stack app you'll need to create two separate applications, which can increase development time.

Delivering a faster application that offers a better user experience is in itself enough reason to give SPA a try, but it’s important to make a careful estimation of time and resources necessary to apply it in your project, to minimize the odds that you regret on this decision later on.

What if you're in a small team with a tight schedule and still want to build an application that works like a SPA? Is it possible to achieve similar results without the complexity added by a front-end framework?

The answer is yes and this post will present you with an alternative to achieve that.

Keeping the heavy work in the server-side with Turbolinks

If you're used to work on a full-stack framework like Ruby on Rails, you must be familiar with rendering the pages in the back-end and sending full HTML pages as response to HTTP requests.

Turbolinks is a JavaScript library that allows you to continue working in a similar fashion, while changing the results you get, making your traditional web application behave like a SPA. Check out how the official documentation describes it:

Get the performance benefits of a single-page application without the added complexity of a client-side JavaScript framework

Ok, but how does Turbolinks work?

By adding Turbolinks to your project you'll notice that when the user clicks on a link the page content will change without a full page refresh. Also, the browser's URL will change according to the link that was clicked.

Turbolinks intercepts all clicks on <a> links to the same domain and prevents the browser to proceed with its default behaviour. Instead, Turbolinks makes a XMLHttpRequest, gets the response and renders it. The server responds with a HTML page ready to be rendered.

During the rendering process, Turbolinks merges the contents of the <head> element and replaces the current <body>. The <html> element, alongside with both the window and document JavaScript objects, persist from one rendering cycle to the other. To change the browser's URL, Turbolinks uses the History API.

The XMLHttpRequest is made in the Turbolinks.visit method that takes a location and an object as parameters. An action can be passed as a String in the argument object’s properties.

There are two types of visits on Turbolinks:

  • Application visits: Performed when the user clicks on a link or when the application directly runs the JavaScript Turbolinks.visit method. The supported actions for this visit type are advance, that will add the new page to the browser's history and keep the previous, and replace, that will substitute the previous page with the new one in the browser's history.
  • Restoration visits: Performed when the user hits the browser's back and forward buttons to navigate. Turbolinks will try to restore a copy of the page from cache, and if it's not possible, it will retrieve that copy over the network. The only supported action for this visit type is restore that can only be used internally, so if you try to perform a visit with the restore action by yourself, it won’t work.

What about form submissions?

Turbolinks doesn't intercept any form submissions. To maintain the SPA behavior you'll need to submit your forms using Ajax. What if you need to respond a form submission with a page redirect? If you're rendering the pages in the server-side you'll need to reload the browser's page, right? Not with Turbolinks.

You can respond to these Ajax submissions with a JavaScript that performs a Turbolinks.visit to the target page, or even respond with a success or error code and perform the Turbolinks.visit in the Ajax callback or promise.

If you're working with Rails it's even easier. You just need to use the redirect_to method and that's it. Rails will render a JavaScript that performs a Turbolinks visit under the hood. It's important to highlight that Rails renders a Turbolinks visit with the replace action, so if you want to keep the previous page in the browser's history you'll need to render the JavaScript by yourself.

Since form submissions tend to change the application state on the server-side, before performing the Turbolinks.visit you should run a Turbolinks.clearCache, that will remove all entries from the Turbolinks page cache.

Some useful features

  • You can disable Turbolinks for some links by adding the data-turbolinks="false" HTML attribute to it.
  • You can define an action to a link adding the data-turbolinks-action="<action>" HTML attribute to it.
  • As mentioned before, both the window and document JavaScript objects persist from one rendering to another. So if you want to run a JavaScript every time a new page is loaded, you need to listen to the turbolinks:load event instead of the default load.
  • There are other useful events like turbolinks:request-start, turbolinks:request-end and turbolinks:render. You can find the full events list in the Turbolinks page.
  • Turbolinks adds a CSS-based progress bar to provide feedback during a request. It's a <div> element with the class name turbolinks-progress-bar. If you want to customise or hide the progress bar you just need to overwrite the .turbolinks-progress-bar class.
  • You can mark elements as permanent using the data-turbolinks-permanent HTML attribute. These elements will persist across page loads. It's useful for elements like navigation menu, header, and footer.

Keep in mind it's not a perfect tool

Turbolinks will always replace the entire <body> during the visit process. It doesn't allow you to replace just pieces of the page like client-side frameworks usually do. Content changed by JavaScript code will be overwritten during a Turbolinks visit. To persist these changes between pages rendering, you'll need to implement a solution by yourself or use a JavaScript framework that provides this feature by default.

Turbolinks maintains a cache of recently visited pages, which is good because your application won't need to access the network during most of the restoration visits. But you need to beware of some possible unexpected behaviours.

Imagine that your JavaScript code adds some custom content to your page when the turbolinks:load event is triggered. When the user clicks on a link to visit a new page, Turbolinks will cache the previous page exactly the way it was when the user clicked on the link. Now, let's imagine the user decides to go back to the previous page using the browser's back button. As mentioned before, Turbolinks will perform a restoration visit under the hood and the turbolinks:load event will be triggered again, then your JavaScript code will add the custom content, producing a bug on your page by duplicating the custom content. To solve this problem you need to check if the custom content was already added, preventing your JavaScript code from performing this twice.

Even though the end result is very close to what a SPA is supposed to be, Turbolinks doesn't turn your application into one. So if you want to build a Progressive Web App (PWA) and offline support is a requirement, Turbolinks is not the way to go here. The same applies if you want to completely separate your front-end from your back-end code and get the benefits of this clear layer split. At the end of the day, it doesn’t matter if we are building a traditional web page using Turbolinks or not, we will still face the same kind of challenges.

As we've seen, SPAs provide a better user experience but it comes with a price and depending on the resources you have, that price could be too high for your team to pay. So before you decide on creating a SPA, analyze if your team will be able to meet the deadline. If the answer is no, or you don't even know the answer, you need to search for alternatives and Turbolinks is a strong one.

What is good about Turbolinks is that the way you build your application will stay almost the same, what will change are the results you'll get, so you can benefit from the experience you already have instead of investing more time to rush through a new framework in a short period of time. But it's important being aware of the limitations. If your application has some requirement that can't be met by using Turbolinks, go and explore other options.

I hope you have learned something new from this post. Choosing the right tools to create your software is as important as actually building it. Knowing the alternatives will help you make the best decision.

--

--