DEV Community

Cover image for Turbolinks and InstantClick Boosting Page Load Speed
Tianya School
Tianya School

Posted on

Turbolinks and InstantClick Boosting Page Load Speed

we’re diving into Turbolinks and InstantClick, two powerful tools that supercharge webpage loading speeds, especially for server-rendered multi-page applications (MPAs). They minimize resource re-parsing and preload content, making page transitions feel as fast as a single-page application (SPA). We’ll break down their principles, usage, and real-world impact with detailed code examples, guiding you step-by-step to master their speed-boosting magic. Our focus is on practical, technical details to help you maximize page load performance!

Why Page Load Speed Matters

Slow page loads ruin user experience. Studies show that if a page takes over 3 seconds to load, 40% of users will bounce. Google’s PageSpeed tests factor load speed into SEO rankings, meaning slow sites lose both users and traffic. Turbolinks and InstantClick tackle this by optimizing navigation and resource loading, making page switches lightning-fast. Let’s start with Turbolinks and explore their mechanics and implementation.

Turbolinks: Making MPAs Feel Like SPAs

Turbolinks is a JavaScript library, originally built for Ruby on Rails but now standalone, usable in any server-rendered web app. Its core idea is to avoid full page reloads by using AJAX to fetch new page content, replacing the current page’s <body> and merging the <head>, eliminating the need to re-parse CSS and JavaScript, thus boosting speed significantly. GitHub: Turbolinks LogRocket Blog

How Turbolinks Works

Turbolinks operates simply:

  • Intercepts <a> tag clicks, preventing default browser navigation.
  • Uses AJAX (XMLHttpRequest) to fetch the target page.
  • Parses the HTML response, replaces the current <body>, and merges differing <script> or <style> tags in <head>.
  • Updates browser history with history.pushState for functional back/forward buttons.
  • Caches visited pages to speed up repeat navigation.

This means CSS and JS are parsed only on the initial load, with subsequent navigations swapping content only, saving significant overhead. Ful.io: Turbolinks

Installing and Configuring Turbolinks

For a simple HTML project, add Turbolinks:

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Turbolinks Demo</title>
  <script src="https://cdn.jsdelivr.net/npm/turbolinks@5.2.0/dist/turbolinks.js"></script>
</head>
<body>
  <a href="page2.html">Go to Page 2</a>
  <h1>Home Page</h1>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
<!-- page2.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Page 2</title>
  <script src="https://cdn.jsdelivr.net/npm/turbolinks@5.2.0/dist/turbolinks.js"></script>
</head>
<body>
  <a href="index.html">Back to Home</a>
  <h1>Page 2</h1>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Add Turbolinks via CDN in <head>, and it auto-initializes, intercepting link clicks. Run http-server (npm install -g http-server), visit index.html, and click the link. Page transitions are near-instantaneous because Turbolinks only swaps the <body>.

Turbolinks Events

Turbolinks alters the traditional page load event model, so $(document).ready (jQuery) or DOMContentLoaded may not work. Use Turbolinks-specific events:

  • turbolinks:load: Fired when a page loads (including from cache).
  • turbolinks:request-start: AJAX request begins.
  • turbolinks:visit: Navigation starts on link click.

Example to log page loads:

document.addEventListener('turbolinks:load', () => {
  console.log('Turbolinks page loaded!');
});
Enter fullscreen mode Exit fullscreen mode

Add this in a <script> tag in <head>, as Turbolinks discards <body> scripts on replacement. Switching pages logs the message in the console. Coderwall: Turbolinks

Turbolinks Caching

Turbolinks caches pages automatically, speeding up repeat visits. Clicking “Back to Home” loads from cache instantly. Control caching manually:

Turbolinks.clearCache(); // Clear cache
Turbolinks.visit('/page2.html', { action: 'replace' }); // Custom navigation
Enter fullscreen mode Exit fullscreen mode

Google Analytics Compatibility

Turbolinks’ AJAX loading doesn’t trigger Google Analytics page tracking. Handle it manually:

<body>
  <script>
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
    ga('create', 'UA-XXXXX-Y', 'auto');
    document.addEventListener('turbolinks:load', () => {
      ga('send', 'pageview');
    });
  </script>
</body>
Enter fullscreen mode Exit fullscreen mode

Trigger ga('send', 'pageview') on turbolinks:load to track page views. Coderwall: Turbolinks

Real-World Example

For a Rails project, add to Gemfile:

gem 'turbolinks', '~> 5.2.0'
Enter fullscreen mode Exit fullscreen mode

In app/assets/javascripts/application.js:

//= require turbolinks
Enter fullscreen mode Exit fullscreen mode

In app/views/layouts/application.html.erb:

<!DOCTYPE html>
<html>
<head>
  <%= javascript_include_tag 'application' %>
</head>
<body>
  <%= link_to 'Page 2', page2_path %>
  <%= yield %>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Run rails server. Navigation is noticeably faster than traditional page loads, as CSS and JS load only once.

Turbolinks Notes

  • Script Placement: Place scripts in <head> to avoid re-execution. LogRocket: Turbolinks
  • Event Binding: Use addEventListener instead of jQuery’s $(element).click to prevent duplicate bindings.
  • Third-Party Libraries: Libraries like Google Analytics require manual event triggers.

InstantClick: Preloading on Hover

InstantClick is similar to Turbolinks but excels with preloading: it starts loading pages on mouse hover (mouseover) or touch (touchstart), so when users click, the page is ready, feeling instantaneous. InstantClick

How InstantClick Works

InstantClick’s core:

  • Preloading: On mouseover (or touchstart on mobile), uses <link rel="prefetch"> or AJAX to load the page.
  • Replaces <body> and <title> via pushState and AJAX (pjax), skipping CSS/JS parsing.
  • Includes a progress bar for feedback on slow loads.
  • Mobile support with touchstart preloading (~300ms on Android, ~450ms on iOS). InstantClick 3.0

Installing and Configuring InstantClick

Add InstantClick to an HTML project:

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>InstantClick Demo</title>
  <script src="http://instantclick.io/instantclick.js" data-no-instant></script>
</head>
<body>
  <a href="page2.html">Go to Page 2</a>
  <h1>Home Page</h1>
  <script>
    InstantClick.init();
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

data-no-instant prevents InstantClick from processing its own script. Run http-server, click a link, and the page switches ultra-fast, as InstantClick preloads on hover.

InstantClick Events

InstantClick events include:

  • instantclick:change: Fired when the page switches.
  • instantclick:preload: Fired when preloading starts.

Example:

InstantClick.on('change', () => {
  console.log('InstantClick page changed!');
});
Enter fullscreen mode Exit fullscreen mode

Add to a <script> tag to log page changes.

Custom Preloading

Set a preload delay to reduce server load:

InstantClick.init(100); // Preload after 100ms hover
Enter fullscreen mode Exit fullscreen mode

For mobile, use mousedown or touchstart:

InstantClick.init('mousedown'); // Preload on mouse down
Enter fullscreen mode Exit fullscreen mode

Progress Bar

InstantClick includes a faux progress bar (like NProgress), customizable with CSS:

#instantclick {
  background-color: #007bff;
  height: 4px;
}
Enter fullscreen mode Exit fullscreen mode

The bar appears during slow loads, giving user feedback. InstantClick 3.0

Google Analytics Compatibility

Like Turbolinks, InstantClick requires manual Analytics tracking:

<body>
  <script>
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
    ga('create', 'UA-XXXXX-Y', 'auto');
    InstantClick.on('change', () => {
      ga('send', 'pageview');
    });
  </script>
</body>
Enter fullscreen mode Exit fullscreen mode

Real-World Example

In a WordPress site, install the InstantClick plugin:

# Assuming a WordPress environment
wp plugin install instantclick --activate
Enter fullscreen mode Exit fullscreen mode

Configure the plugin, setting data-no-instant on scripts like Analytics. Navigation becomes noticeably faster, with hover preloading making switches feel instant. Keetrax: InstantClick

InstantClick Notes

  • Script Conflicts: <body> scripts may re-execute; use data-no-instant to exclude them. Keetrax: InstantClick
  • Preload Overhead: Hover preloading may increase server requests; use a delay (e.g., 100ms) to balance.
  • Mobile: touchstart preloading is mobile-friendly but requires optimized server responses.

Combining Turbolinks and InstantClick

Turbolinks and InstantClick share similar goals, but InstantClick’s preloading is more aggressive. Combining them—using InstantClick’s preloading with Turbolinks’ page replacement—can be powerful. Here’s a try:

<!DOCTYPE html>
<html>
<head>
  <title>Turbolinks + InstantClick</title>
  <script src="https://cdn.jsdelivr.net/npm/turbolinks@5.2.0/dist/turbolinks.js"></script>
  <script src="http://instantclick.io/instantclick.js" data-no-instant></script>
</head>
<body>
  <a href="page2.html">Go to Page 2</a>
  <h1>Home Page</h1>
  <script>
    document.addEventListener('turbolinks:load', () => {
      InstantClick.init(100);
      InstantClick.on('change', () => {
        console.log('Page changed with InstantClick!');
      });
    });
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Issue: Turbolinks and InstantClick’s AJAX mechanisms can conflict, causing duplicate requests. A community solution uses hoverintent for preloading:

document.addEventListener('turbolinks:load', () => {
  const links = document.querySelectorAll('a:not([data-no-turbolink])');
  links.forEach(link => {
    link.addEventListener('mouseover', () => {
      setTimeout(() => {
        const prefetch = document.createElement('link');
        prefetch.rel = 'prefetch';
        prefetch.href = link.href;
        document.head.appendChild(prefetch);
      }, 100);
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

This code adds <link rel="prefetch"> on hover after a 100ms delay, mimicking InstantClick’s preloading while letting Turbolinks handle page replacement. Run it, and hover preloading plus Turbolinks’ efficiency makes clicks feel instant. Mskog: Turbolinks + Prefetch

Performance Comparison

Test Turbolinks and InstantClick with a simple MPA.

Test Pages

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Performance Test</title>
  <script src="https://cdn.jsdelivr.net/npm/turbolinks@5.2.0/dist/turbolinks.js"></script>
  <script src="http://instantclick.io/instantclick.js" data-no-instant></script>
  <style>
    body { font-family: Arial; }
    .container { max-width: 800px; margin: 0 auto; padding: 20px; }
  </style>
</head>
<body>
  <div class="container">
    <a href="page2.html">Go to Page 2</a>
    <h1>Home Page</h1>
    <p>Large content...</p>
  </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
<!-- page2.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Page 2</title>
  <script src="https://cdn.jsdelivr.net/npm/turbolinks@5.2.0/dist/turbolinks.js"></script>
</head>
<body>
  <div class="container">
    <a href="index.html">Back to Home</a>
    <h1>Page 2</h1>
    <p>Large content...</p>
  </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Add a large image to simulate resource loading:

<img src="https://via.placeholder.com/2000x1000" alt="Test">
Enter fullscreen mode Exit fullscreen mode

Testing Turbolinks

Use only Turbolinks in index.html, run http-server, and measure with Chrome DevTools’ Network panel:

  • Initial load: ~500ms (CSS, JS, image).
  • Navigate to page2.html: ~200ms (only HTML and image).

Turbolinks skips CSS/JS parsing, significantly boosting speed.

Testing InstantClick

Use only InstantClick:

<script>
  InstantClick.init(100);
</script>
Enter fullscreen mode Exit fullscreen mode

Test:

  • Hover 100ms to preload, click switches in <50ms (page cached).
  • Initial load ~500ms, similar to a standard page.

InstantClick’s preloading makes clicks feel instantaneous, like an SPA.

Combined Test

Use the Turbolinks+hoverintent code:

  • Hover preloading + Turbolinks replacement, click switches in <50ms.
  • Initial load ~500ms, subsequent navigations ultra-fast.

The combination achieves near-zero perceived delay, with preloading handling content fetch and Turbolinks optimizing replacement. Mskog: Turbolinks + Prefetch

Real-World Scenario: Complex Rails App

Simulate a blog app with Rails, Turbolinks, and InstantClick.

Rails Setup

Create a Rails project:

rails new blog-app
cd blog-app
Enter fullscreen mode Exit fullscreen mode

Add to Gemfile:

gem 'turbolinks', '~> 5.2.0'
Enter fullscreen mode Exit fullscreen mode

In app/assets/javascripts/application.js:

//= require turbolinks
Enter fullscreen mode Exit fullscreen mode

Generate a controller and views:

rails generate controller Posts index show
Enter fullscreen mode Exit fullscreen mode

In app/views/layouts/application.html.erb:

<!DOCTYPE html>
<html>
<head>
  <%= javascript_include_tag 'application' %>
  <script src="http://instantclick.io/instantclick.js" data-no-instant></script>
  <%= stylesheet_link_tag 'application', media: 'all' %>
</head>
<body>
  <nav>
    <%= link_to 'Home', posts_path %>
    <%= link_to 'Post', post_path(1) %>
  </nav>
  <%= yield %>
  <script>
    InstantClick.init(100);
    document.addEventListener('turbolinks:load', () => {
      const links = document.querySelectorAll('a:not([data-no-turbolink])');
      links.forEach(link => {
        link.addEventListener('mouseover', () => {
          setTimeout(() => {
            const prefetch = document.createElement('link');
            prefetch.rel = 'prefetch';
            prefetch.href = link.href;
            document.head.appendChild(prefetch);
          }, 100);
        });
      });
    });
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

In app/views/posts/index.html.erb:

<h1>Blog Posts</h1>
<% 10.times do |i| %>
  <p>Post <%= i + 1 %>: <%= link_to 'View', post_path(i + 1) %></p>
<% end %>
Enter fullscreen mode Exit fullscreen mode

In app/views/posts/show.html.erb:

<h1>Post <%= params[:id] %></h1>
<p>Large content...</p>
<img src="https://via.placeholder.com/2000x1000" alt="Post">
Enter fullscreen mode Exit fullscreen mode

Run rails server and visit localhost:3000/posts:

  • Initial load ~600ms (includes image).
  • Hover links for 100ms to preload, click switches in <50ms.
  • Back/forward uses Turbolinks cache, nearly instant.

Performance Analysis Tools

Use Chrome DevTools’ Performance panel:

  • Timeline: Check turbolinks:load and instantclick:change events for switch times.
  • Network: Observe AJAX requests and <link rel="prefetch"> preloading.

Run Lighthouse for performance:

  • Turbolinks: First Contentful Paint (FCP) ~200ms for subsequent pages.
  • InstantClick: Click-to-switch <50ms, perceived speed is exceptional.

Conclusion (Technical Details)

Turbolinks and InstantClick make MPA navigation as fast as SPAs using AJAX and preloading. Turbolinks replaces <body>, skipping CSS/JS parsing; InstantClick preloads on hover for near-instant clicks. Combining them achieves near-zero perceived delay. The code examples demonstrated:

  • Turbolinks installation, events, caching, and Analytics integration.
  • InstantClick preloading, progress bar, and mobile support.
  • Combining both with hoverintent for efficient preloading.

Run these examples, check the timeline in DevTools, and experience the silky-smooth page transitions!

Top comments (0)