Originally posted on the Ninjality blog.
In a world of single-page apps (SPAs) that are rendered by the browser, it is hard to consider building any other way; you get a lot of benefits such as performance of almost instant-loading pages. However, there are still good use cases for building traditional server-rendered websites such as those powered by a CMS (content management system) like WordPress. At Ninjality, we recently redesigned and chose to use October CMS to help with managing content. Choosing a CMS doesn't mean that you have to give up on the benefits of SPAs, so I want to show how we used the Turbolinks library to make our site feel like it was built in a modern JavaScript framework.
Understanding PJAX
To help better understand what Turbolinks does, it is important to learn the concept of PJAX which has actually been around for many years. PJAX stands for pushState + Ajax, and uses both those technologies to intercept link clicks so that JavaScript is able to update the URL and load content via Ajax. Initially, both Turbolinks and PJAX required backend integration so that the Ajax responses would only contain the page title and content. While this is still ideal for performance, Turbolinks as a JavaScript-only library simply works out of the box by manually parsing the title and content out of the full HTML. A fun fact if you're new to Turbolinks is that it used to exist as a Rails-only Ruby gem, and PJAX had a JS library that relied on jQuery. Now, Turbolinks is a standalone library that you can integrate into any website.
Installing Turbolinks
You simply need to include the Turbolinks JS library into your page. View the README for instructions, but let's assume that you're using some form of bundler with npm:
npm install turbolinks --save
Now you need to import and initialize Turbolinks:
var Turbolinks = require('turbolinks')
Turbolinks.start()
If your site is very simple, then you're already done. Most of the time you'll need some extra setup though.
Integrating your website
If you already have some JavaScript running on your site, then you're most likely listening for a page load or domready event. In the case of Ninjality, we have code highlighting, smooth scroll, image zoom, share buttons, and many other libraries that would normally look something like this:
document.addEventListener('DOMContentLoaded', function () {
// initialize scripts....
})
However, now that pages can be loaded over Ajax, we need another event to know when those pages load, otherwise our scripts will only fire once (when the user manually visits the site for the first time). Thankfully Turbolinks provides such an event:
document.addEventListener('turbolinks:load', function () {
// initialize scripts....
})
As you can see, a simple change from DOMContentLoaded
to turbolinks:load
will fix your scripts. Well, most of them. Let's fix some common gotchyas in the next section.
Some common gotchyas
You're most likely using Google Analytics, Disqus, social widgets, and other third-party tools that are loaded via inline <script>
snippets. How do you transfer these over to the turbolinks:load
event?
You'll need a dynamic script loader. The load-script library is what we use on Ninjality. As usual, install it through npm:
npm install load-script --save
Now you can use Google Analytics again:
var Turbolinks = require('turbolinks')
var loadScript = require('load-script')
Turbolinks.start()
document.addEventListener('turbolinks:load', function () {
// initialize scripts...
// Google Analytics
if (typeof ga === 'undefined') {
loadScript('https://www.google-analytics.com/analytics.js', function () {
window.ga = window.ga || function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date
ga('create', 'YOUR-TRACKER-ID', 'auto')
ga('send', 'pageview')
})
} else {
ga('send', 'pageview')
}
})
Since we integrate Facebook Comments, the same had to be done here as well:
if (typeof FB === 'undefined') {
loadScript('https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.8&appId=YOUR-FB-APP-ID')
} else {
FB.XFBML.parse()
}
Unfortunately, we had to take an extra step to get Facebook Comments to work properly. This meant including the data-turbolinks-permanent
attribute on <div id="fb-root">
:
<div id="fb-root" data-turbolinks-permanent></div>
Finally, the last gotchya you need to be careful of is when including scripts in the <body>
. Make sure that you add the data-turbolinks-eval="false"
attribute:
<script src="bundle.js" data-turbolinks-eval="false"></script>
While Turbolinks claims to properly resolve inline scripts, we had issues with our JavaScript running multiple times during page navigations.
Closing thoughts
For many content-based websites that need administrative features, a front end framework like React is overkill. October CMS allows us to have a simple setup with search engine optimization and an admin interface in mind. By integrating Turbolinks, we also were able to compromise between the benefits of a server-rendered website and a single-page app. Do keep in mind that Turbolinks is not a replacement for building an SPA, because you don't get other benefits like state management or quick UI development with Turbolinks. Leave a comment or tweet @NinjalityDesign if this article helped you integrate Turbolinks into a project!
Top comments (1)
Such an amazing article. You know the first time my tech lead used it, I felt like, that's magic :)