Turbolinks Cheatsheet

written

A cheatsheet for the often confusing behaviour of Turbolinks.

How it works

When you follow a link, Turbolinks automatically fetches the page:

  • Swaps in its <body>
  • Merges its <head>

…without incurring the cost of a full page load

Allows the server to respond with a full HTML page.

Works in all modern desktop and mobile browsers. Depends on the

  • HTML5 History API
  • Window.requestAnimationFrame

In unsupported browsers, Turbolinks gracefully degrades to standard navigation.

Installation

<script> tag or a traditional concatenated JavaScript bundle
Automatically initializes itself when loaded
CommonJS or AMD module
Require the module, then call the provided start() function.
Ruby on Rails
Can use the turbolinks RubyGem to install Turbolinks

# Gemfile
'turbolinks', '~> 5.2.0’

// application.js
//= require turbolinks

Troubleshooting

First identify if it’s a problem with:

Page Load:

  • Initial page load (Uncommon - basically standard browser behaviour)

Advance or Replace:

  • Displaying the preview of an application visit (flash of content or styling when navigating to pages the browser has already been to, or using the browser’s Forward button)
  • After new page has been downloaded and applied

Restoration:

  • Retrieving page from cache for restore visits (using the browsers Back button)

and then consult the relevant part of the table below.

Cheatsheet

Type of visit
Page Load
Advance or Replace
Restoration
Occurs when
First page is loaded or browser is refreshed
Default: Clicking <a href> to the same domain, or calling Turbolinks.visit(location)

Using replace instead of advance:

<a href="/edit" data-turbolinks-action="replace">Edit</a>

Turbolinks.visit("/edit", { action: "replace" })
Browser’s Back or Forward buttons

Internal operation: don’t  annotate links or invoke Turbolinks.visit with an action of restore.
Disabling

Limit Turbolinks to only a certain path across entire page

Only load same-origin URLs that are prefixed with this path.

<head>
  ...
  <meta name="turbolinks-root" content="/app">
</head>

Disable for part of document

<a href="/" data-turbolinks="false">Disabled</a>

<div data-turbolinks="false">
  <a href="/">Disabled</a>
</div>

Re-enable when ancestor has opted out:

<div data-turbolinks="false">
  <a href="/" data-turbolinks="true">Enabled</a>
</div>

Cancel at runtime

Listen to event turbolinks:before-visit and use event.data.url (or $event.originalEvent.data.url, when using jQuery) to decide whether to call event.preventDefault().

Disable showing a preview (if one is available) while page is being refetched (still used for restoration visits):

<head>
  ...
  <meta name="turbolinks-cache-control" content=“no-preview">
</head>
Disallowing caching

<head>
  ...
  <meta name="turbolinks-cache-control" content="no-cache">
</head>

Force a full reload:

<head>
  ...
  <meta name="turbolinks-visit-control" content="reload">
</head>

Cancel at runtime

Does not fire turbolinks:before-visit, so can’t be cancelled in this way
Navigation

Prevents the browser from following the link

Uses the History API to push a new entry onto the browser’s history stack (changes current URL): history.pushState.

Request:

Always requests the new page using XMLHttpRequest
Discards the topmost history entry: history.replaceState
Scroll Position

If location includes an anchor, attempts to scroll to the anchored element. Otherwise, it will scroll to the top of the page.
Saves scroll position before navigating away and automatically returns to this saved position on restoration visits.
Events & Event listeners
turbolinks:load is fired in addition to standard:
Can’t depend on a full page load to reset your environment every time you navigate.

Handle before page change

document.addEventListener("turbolinks:before-visit", function() {
  // ...
})

Handle after page change

turbolinks:load is fired again after every page change
  • Can be used as DOMContentLoaded replacement
  • Don’t use to add event listeners directly to elements on the page body: Use event delegation to register event listeners once on document or window.

document.addEventListener("turbolinks:load", function() {
  // ...
})
Copies the page to cache using cloneNode(true), so when it’s returned from the cache, attached event listeners and associated data are discarded.

Handle cache push

E.g. Reset forms, Collapse expanded UI elements, Tear down third-party widgets

document.addEventListener("turbolinks:before-cache", function() {
  // ...
})

Handle cache pop

Turbolinks adds a data-turbolinks-preview attribute to the <html> element when it displays a preview from cache.

if (document.documentElement.hasAttribute("data-turbolinks-preview")) {
  // Turbolinks is displaying a preview
}
Rendering
General

Will render a preview of the pagefrom cache immediately after the visit starts, if possible
Renders copy of the page from cache without making a request if possible
window and document objects
Reloaded from clean state
Persisted (No change)

Retain their state across page changes, along with other objects you leave in memory.
<html>

Persisted (No change)

Adds a data-turbolinks-preview attribute when displaying a preview from cache
Replaced

Merge current document contents with cached document

Mark elements as permanent so they’re not reverted  when the user navigates back (e.g. shopping cart counter):
  • Elements must have an id
  • Before each render, Turbolinks matches all permanent elements by id and transfers them from the original page to the new page, preserving their data and event listeners.

<div id="cart-counter" data-turbolinks-permanent>1 item</div>

<head>
Browsers loads and evaluates any <script> elements 
Merged

Appends any new <script> elements in the new page’s <head> which aren’t in the current one (causing them to be loaded and evaluated) 

Load your application’s JS bundle using <script> elements in the <head> of your document to avoid reloading them with every page change.
  • Use <script defer> instead of putting scripts at the end of <body>

Force page reload if assets change

Track asset URLs in <head> between pages and automatically issue a full reload if they change

<head>
  ...
  <link rel="stylesheet" href="/application-258e88d.css" data-turbolinks-track="reload">
  <script src="/application-cbd3cd4.js" data-turbolinks-track="reload"></script>
</head>
<body>
Replaced

Evaluates <script> elements in a page’s <body> each time it renders the page.

Use inline body scripts to:
  • Set up per-page JavaScript state
  • Bootstrap client-side models

Don’t use to (use the turbolinks:load event instead):
  • Install behavior, or
  • Perform more complex operations when the page changes

Annotate <script> elements with data-turbolinks-eval="false" if you do not want Turbolinks to evaluate them after rendering.
  • Won’t prevent your browser from evaluating scripts on the initial page load.

Comments