Turbolinks Caching with JavaScript Modified Dom

Avoid duplicated markup by preparing your document for caching

Code Web Design by Simplu27 is licensed under CC0

Turbolinks takes advantage of a lot of benefits of a single page application without needing to actually build or add the complexity of a client-side JavaScript framework. One of the ways it achieves the snappy page loads of a single page application in a traditional, link-based application is caching. When a page is visited, Turbolinks will cache the final state of the visited page. That includes caching the final markup of the document, the stylesheets, image assets, and JavaScript.

Caching Creates Duplicate Markup

With many plugins, a traditional web application will initialize JavaScript plugins on page load and those plugins will often create and maintain its own markup, and event listeners, and handlers. In many cases, this works great, even in a Turbolinks-enabled application. Sometimes, though, the caching can create duplicate markup.


The application I am working on has a form that utilizes Chosen to replace some of the select boxes. Chosen is initialized on page load, looks for select tags that are tagged as Chosen selects, and then applies itself on the dom by creating new markup while setting up listeners and handlers for those selectors.

In Turbolinks, the resulting cached version of a page, post-Chosen, will include all of the new markup created, along with assets. When a user navigates normally through the app, Turbolinks displays the cached version as a preview while fetching a new version of the page. Turbolinks then replaces the cached body with the new body while merging the headers. In restoration visits, when a user navigates using the forward and back browser buttons, Turbolinks only displays the cached version but will rerun the (cached) styles and JavaScript initializers.

The result is that when the cached version of the markup is rendered, it will include the original Chosen markup. However, that markup is now orphaned since the JavaScript listeners and handlers are no longer linked. When the JavaScript is rerun, Chosen believes it needs to initialize again and therefore creates duplicate markup.

The fix is pretty simple in this case. Per the documentation, you can prepare the document prior to Turbolinks caching it.

document.addEventListener("turbolinks:before-cache", function() {
// clean up dom

You can do all sorts of things in here. In our case, we wanted to stop Chosen from orphaning markup. To do this we simply destroy all instances of Chosen prior to cache, and when it loads, let it reinitialize.

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

That’s it!


In most cases, this solution will work and it is as easy as replacing your typical ready or onload function with the turbolinks:load

document.addEventListener("turbolinks:load", function() {
// do stuff here

The caveat is that any setup you do put in here, or if you keep it in your traditional ready function, it needs to be idempotent, or in other words, safe to run multiple times without changing the result beyond its initial application. You can read the Turbolinks documentation on ensuring your setup is idempotent.

In our example, this wouldn’t entirely work because the event listeners and handlers were no longer linked to the markup and Chosen does not offer a way to relink orphaned markup. There would only be a single set of markup for Chosen, but it would not be functional.

Photo of Laura Mosher

Laura is enthusiastic about learning new languages, concepts, and tools in the software development industry. When she’s not working to build software solutions for clients, she’s developing and maintaining Collective Idea’s internship program.


Post a Comment

(optional — will be included as a link.)
  1. Thank you laura !! I tried for 1 week to find the solution.

    April 26, 2017 at 9:31 AM