Navigation

Learn how navigation within Response works on the frontend.

In Response, when you click a link you'll be taken to the next page without a page refresh under most conditions. If you're familiar with building single-page applications, you might think that Response is built using the same technologies when it is not. To offer refresh-less frontend navigation, much like that of a single-page application, we utilize the Turbolinks library.

The library, which comes in at about 70kb in size, speeds up routing by pre-fetching links and merging them with the existing HTML already in the browser. Under most conditions this works without a hitch and you won't need to annotate links or add any additional JavaScript for navigation, however there are a few concepts we think you should understand.

Navigating

Turbolinks intercepts on <a href> links to the same domain that Response is on. When you click an eligible link, Turbolinks prevents the browser from following it. Instead, Turbolinks changes the browser's URL using the exposed history API, requests a new page using XMLHttpRequest 🔗, and then renders the HTML response.

During rendering, Turbolinks replaces the current <body> element outright and merges the contents of the <head> element. The JavaScript window and document objects, and the HTML <html> element, persist from one rendering to the next.

Turbolinks treats each navigation as a visit to a location with an action.

Visits in Turbolinks represent the entire navigation lifecycle from click to render. That includes changing the browser history, issuing the network request, restoring a copy of the page from cache, rendering the final response, and updating the scroll position.

There are two types of visits: an application visit, which has an action of advance or replace, and a restoration visit, which has an action of restore.

Advance vs Replace

By default Turbolinks performs an advance action that will use the browser's history.pushState functionality to add the navigation to the browser's history stack.

You may wish to navigate to a new page without pushing a new navigation entry onto the browser's history stack. To allow this behavior Trubolinks offers a second replace action which will utilize the browser's history.replaceState functionality to discard the topmost history entry and replace it with the new location.

To do this, you can use either a data- directive on the link or the Turbolinks JavaScript API to navigate using the replace action.

<a href="/edit" data-turbolinks-action="replace">Edit</a>
// programatically navigate using JavaScript
Turbolinks.visit('/edit', { action: 'replace' })

Restoring Pages

Turbolinks has built-in support for the browser's back and forward buttons, which means that when you click the back button Turbolinks will initiate a restoration visit from the browser's history stack. If possible, Turbolinks will restore the page from its cache without making a request over the network to the Response backend. Learn more about caching here.

When restring the pages using the back and forward buttons, Turbolinks will restore the scroll position as well. If the user scrolled down on the page before navigating away, their previous scroll position will be restored when they perform a restoration visit.

You might be tempted to use the restore action when performing a visit using the data-turbolinks-action directive or the Turbolinks.visit API, however this should not be done.

Cancelling Visits

If you wish to monitor visits between pages and cancel the navigation, regardless of how they are performed, you can do so by listening for the turbolinks:before-visit event. The event receives a standard event object, however, the data.url property will include the URL Turbolinks will route to. To prevent the visit from occurring, simply call the preventDefault() method on the event object.

document.addEventListener('turbolinks:before-visit', (event) => {
  if (event.data.url === '/my-url') {
    // stop all visits to /my-url through Turbolinks
    event.preventDefault()
  }
})

Disabling Turbolinks

From time to time you may want to have a link function like a normal link. This is possible by annotating the link with data-turbolinks="false". This will disable Turbolinks on all ancestors as well.

<a href="/my-link" data-turbolinks="false">Does Not Use Turbolinks</a>

If you want to enable Turbolinks on a link when an ancestor has opted out, use data-turbolinks="true".

<div data-turbolinks="false">
  <a href="/">does not have turbolinks</a>
  <a href="/about" data-turbolinks="true">has turbolinks</>
</div>

Caching

As mentioned previously, Turbolinks caches pages you visit and restores them when performing a restoration visit from the history stack. Turbolinks also uses the cache to improve perceived performance by showing temporary previews during an application visit.

When navigating by history (using Restoration Visits), Turbolinks restores the page from cache without accessing the network, if possible.

During standard navigation Turbolinks will immediately restore the page from cache and display it as a preview while simultaneously loading a fresh copy of the page from the network. This lets the user see the page as its fetched, giving the illusion of instant page loads for frequently accessed locations.

Trubolinks saves a copy of the current page to cache before rendering a new page. The page is copied using cloneNode(true), which means any attached event listeners and associated data are discarded from the cache.

Preparing a Page for Caching

Since Turbolinks caches the page before leaving, it's best practice to clear forms and other page-specific items so that the page is ready for caching. Currently, Response automatically does the following things before Turbolinks caches the page:

  1. Clears all forms. To do this we find all <form> elements on the page, then call .reset() on each individual form. If you wish for the cache to retain the form values, simply add data-no-reset to the form attribute.

  2. Hides all dropdown components. The dropdown component in Response will automatically add the necessary components to do this, however, if you have your own dropdown implementation (not recommended) you'll need to handle this yourself.

If you need to implement additional clean-up functionality, listen for the turbolinks:before-cache event and perform the necessary logic in your event listener. The following example is a simplified version of how Response clears all forms before the page is cached. You will not need to implement this functionality.

// Turbolinks is about to cache the page
document.addEventListener("turbolinks:before-cache", () => {
  // Locate all forms on the page
  const forms = document.querySelectorAll('form')
  // For each form on the page
  forms.forEach((form) => {
    // Reset it
    form.reset()
  })
})

Detecting Cached Pages

Sometimes it's necessary to know if the user is viewing a page from the Turbolinks cache. To determine this, check for the data-turbolinks-preview attribute on the document element itself.

if (document.documentElement.hasAttribute('data-turbolinks-preview')) {
  // The page is from the cache
} else {
  // The page was fetched from the network 
}

Persisting Elements

If you have an element that is persistent across a number of pages, it makes sense to preserve the element instead of replacing it with the same element, losing it's content and associated event handlers or data. Turbolinks makes this a cinch by annotating the element you wish to preserve with data-turbolinks-permanent. It's important to note, though, that a requirement to using the data-turbolinks-permanent is annotating your item with an id.

❗️

Note:

The HTML specification states that a single id can not be used more than once per page. Carefully ensure that and id's you provide do not already exist as persisting elements will likely fail.

By providing both the id and the data-turbolinks-permanent attribute, Turbolinks will match the permanent item by the ID and preserve the content itself as well as the data and event listeners associated with the element.

<div id="online-users-counter" data-turbolinks-permanent>1</div>

JavaScript

Turbolinks will automatically evaluate inline script blocks on a per-page basis. If you have JavaScript to include on your specific page that can't be done with Alpine.js, consider using a <script /> block on the page itself. You can easily add this in your Blade templates by using the @push directive. We cover that more in the Templating section.

Since Turbolinks handles JavaScript between pages, if you are installing global functionality on a large number of pages, we recommend simply pushing a JavaScript file to all pages (which can be done from your extension manifest itself). If you are not using our helper to push JavaScript for your extension, you can annotate the <script> tag with data-turbolinks-track="reload" to force a full page reload if the JavaScript file changes. To take full advantage of this, we suggest that you version your JavaScript files, which can be done for you using the included webpack.mix.js from the extension template.

Events

Turbolinks emits a number of events designed to allow you to hook into functions before they are performed by Turbolinks. Except where noted, Turbolinks fires events on the document object.

  • turbolinks:click fires when you click a Turbolinks-enabled link. The clicked element is the event target. Access the requested location with event.data.url. Cancel this event to let the click fall through to the browser as normal navigation.

  • turbolinks:before-visit fires before visiting a location, except when navigating by history. Access the requested location with event.data.url. Cancel this event to prevent navigation.

  • turbolinks:visit fires immediately after a visit starts.

  • turbolinks:request-start fires before Turbolinks issues a network request to fetch the page. Access the XMLHttpRequest object with event.data.xhr.

  • turbolinks:request-end fires after the network request completes. Access the XMLHttpRequest object with event.data.xhr.

  • turbolinks:before-cache fires before Turbolinks saves the current page to cache.

  • turbolinks:before-render fires before rendering the page. Access the new <body> element with event.data.newBody.

  • turbolinks:render fires after Turbolinks renders the page. This event fires twice during an application visit to a cached location: once after rendering the cached version, and again after rendering the fresh version.

  • turbolinks:load fires once after the initial page load, and again after every Turbolinks visit. Access visit timing metrics with the event.data.timing object.