Migrate an existing app to Turbolinks
Tags: turbolinks, javascript, rails, frontend
Published: 2020-05-08 21:00:38

A few months ago we decided at [Tefter](https://tefter.io) to use Turbolinks with our main Ruby on Rails app, so we can take advantage of its features for better SPA look & feel and faster page loading - at least from the user perspective. Back then we had some Javascript files and we relied heavily on jQuery.

## About Tefter

![Tefter logo](https://tefter.io/tefter.png)

Before going into more detail about the specifics of the migration, let's talk about Tefter first. It's a tool aiming to optimize your web surfing routine, a combination of personal search-engine, a social bookmarking tool, and a place to archive stuff to read later and write notes. One would interact with Tefter through the [Web app](https://tefter.io/), [the browser extension](https://chrome.google.com/webstore/detail/tefter/eldofalegbgagpenjjcapjaogpioldoh), the [mobile](https://twitter.com/Tefter_io/status/1106145149019742210), and the [desktop apps](https://github.com/tefter/desktop) or [Slack](https://slack.com/apps/AFBC4A147-tefter)!

## Installation 

The main [repo](https://github.com/turbolinks/turbolinks) has a ton of documentation that explains everything. Also, [this](https://www.honeybadger.io/blog/turbolinks/) post from the Hobeybadger team help us a lot at the beginning of the migration.

In our `Gemfile` we added:

```ruby
gem 'turbolinks'
```

And installed it with Yarn:

```bash
yarn add turbolinks
```

Then in our `packs/application.js` file we import and start Turbolinks:

```js
import Turbolinks from "turbolinks"
Turbolinks.start()
```

## Migration 

The steps we did for migration to work were the following:

* We changed all `document.ready` with `turbolinks:load` events.
  
```javascript
// Old
$(document).ready(function() { ...

// New
$(document).on('turbolinks:load', function() { ...
```
* And all `DOMContentLoaded` events with `turbolinks:load`  

* We moved all logic from `window.onload` events to `turbolinks:load`

* We did the same with `window.resize` events.

* We made sure to clear Turbolinks cache before ajax requests

```javascript
// Clear Turbolinks cache before ajax requests
document.addEventListener('ajax:before [data-remote]', () => {
  Turbolinks.clearCache();
});
```

* We disabled Turbolinks for Facebook, Google, Slack, and Github omni-oauth sign up pages.

* We disabled Turbolinks from other 3rd parties calls like Pocket authorization and Slack installation.

* We noticed that we had problems with rendering some Javascript components like modals or tooltips (we use for example [Micromodal](https://micromodal.now.sh/) and [tippy](https://github.com/atomiks/tippyjs)). For those cases, we explicitly opted out from Turbolinks:

```html
<div class="modal" data-turbolinks="false">
```
or 

```ruby
<%= link_to '#', class: 'tippy', data: { turbolinks: false } do %>
```

* We experienced some rendering issues with views with different layouts. We should investigate these issues further but because there was only a simple case (our Readable view) we decided to opt out Turbolinks from it.

* We had some hard times dealing with event listeners loaded inside `turbolinks:load` functions. We ended up using [Stimulus](https://stimulusjs.org/) and handle them inside `connect()` functions 

* In one case we wanted to make an ajax call and we discovered that we should add the security token.

```javascript
$.ajax({
        url: someUrl,
        method: 'POST',
        beforeSend: function(xhr) { 
                     xhr.setRequestHeader('X-CSRF-Token', 
                     $('meta[name="csrf-token"]').attr('content'))
                    },
```

* We found some inconsistencies when we use `turbolinks:onload` and async javascript loading. We switched to `defer`.


## Feelings 

We have a small JavaScript codebase but the overall migration and bug fixing took us some time mainly due to our inexperiences with the Turbolinks architecture and its expectations. Overall the sign is positive. If you have an existing codebase be patient, there will be plenty of small bugs. Fortunately, one should get quickly familiar with solving them.

## What you should consider next

* [StimulusJS](https://stimulusjs.org/)
* [My Tefter list for Turbolinks](https://www.tefter.io/chief/lists/turbolinks)
* [My Tefter list for StimulusJS](https://www.tefter.io/chief/lists/stimulusjs)