Using Google Analytics's gtag.js with Turbolinks
Tags: turbolinks, analytics, javascript
Published: 2022-02-10 04:33:20

---
title: Using Google Analytics's gtag.js with Turbolinks
published: true
description: Track subsequent page visits with Google Analytics's gtag.js when using Turbolinks
tags: turbolinks, analytics, javascript
//cover_image: https://direct_url_to_image.jpg
---

If you use the default Google Analytics Global Site Tag (gtag.js) code on a Turbolinks-powered site, Analytics will only track the first page that a user visits. As a user navigates to other pages, none of the subsequent pages will be tracked.

This is because Google Analytics was designed for traditional multi-page websites, where each page navigation causes a complete reload of the page. When the new page loads, the Analytics code in the head will fire almost immediately.

When using Turbolinks, the code in the head only executes on the initial page load. This means we must do some extra work to ensure that Analytics is notified when navigating between pages.

## Programmatically sending page views to Analytics using Turbolinks

Below is the Global Site Tag (gtag.js) code that Analytics provides in its dashboard under **Admin > Tracking Info > Tracking Code**.

![Where to find your tracking code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k692jbohctiaqz7ez69e.png)

![Copy the tracking code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/owznemnulgss0llwwli8.png)

Copy this code from Analytics and paste it as the first item inside your page's `<head />` tag. In your code, `{ANALYTICS_ID}` will be something similar to `UA-123456789-1`.

```html
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={ANALYTICS_ID}"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', '{ANALYTICS_ID}');
</script>
```

_If your script doesn't look like the one above, you may be using analytics.js or Google Tag Manager. Those are beyond the scope of this article, but you can read more about how to get these to work with single page applications [here](https://www.bounteous.com/insights/2018/03/30/single-page-applications-google-analytics/)._

**Immediately below the script tag above,** add the following, replacing `{ANALYTICS_ID}` with your site's Analytics ID:

```html
<script type="module">
  let isInitialLoad = true;
  document.addEventListener('turbolinks:load', () => {
    if (isInitialLoad){isInitialLoad = false; return;}
    gtag('config', '{ANALYTICS_ID}', {
      page_path: window.location.pathname,
    });
  });
</script>
```

## The full solution

All together, your site's head will now look like this:

```html
<head>
  <!-- Global site tag (gtag.js) - Google Analytics -->
  <script async src="https://www.googletagmanager.com/gtag/js?id={ANALYTICS_ID}"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());

    gtag('config', '{ANALYTICS_ID}');
  </script>
  
  <script type="module">
    let isInitialLoad = true;
    document.addEventListener('turbolinks:load', () => {
      if (isInitialLoad){isInitialLoad = false; return;}
      gtag('config', '{ANALYTICS_ID}', {
        page_path: window.location.pathname,
      });
    });
  </script>

  <!-- More code below... -->
</head>
```

**Once you've replaced `{ANALYTICS_ID}` with your site's ID, Analytics will now be able to track subsequent page visits on your website.** Keep reading if you want to understand how this code works, or feel free to copy it and be on your way!

## How it works

Let's break this code apart, starting with the script tag that loads `gtag.js`:

```html
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={ANALYTICS_ID}"></script>
```

This loads the analytics script. The interesting part here is the `async` attribute: this script will load asynchronously in the background while the other HTML and JS continues to be parsed.

Next, let's look at the boot-up script:

```html
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', '{ANALYTICS_ID}');
</script>
```

The most important piece of this script is `gtag('config', '{ANALYTICS_ID}')`. Calling the `gtag()` function with `'config'` in its first argument tells Analytics that a new page view has occurred.

The `gtag()` function is also interesting: it pushes any arguments that it receives to a global `dataLayer` array. Once the async gtag.js script finishes loading, the items within the `dataLayer` array will be pushed to Analytics.

Now lets turn our attention to Turbolinks:

```html
<script type="module">
  let isInitialLoad = true;
  document.addEventListener('turbolinks:load', () => {
    if (isInitialLoad){isInitialLoad = false; return;}
    gtag('config', '{ANALYTICS_ID}', {
      page_path: window.location.pathname,
    });
  });
</script>
``` 

On the `<script />` tag, the `type="module"` attribute does two things:

1. It prevents variables within the script from leaking into the global scope, meaning we don't need to worry about other scripts overriding them.
2. It defers the script, meaning that it will only fire after the document has been parsed, but before the `DOMContentLoaded` event is fired ([MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)). This delayed execution is fine, because it will still load before Turbolinks initializes.

In the `turbolinks:load` callback, we check to see if it is the initial page load, and if it is, we return early. The first visit is already tracked from the script we copied from the Analytics dashboard.

On page navigations, the `turbolinks:load` callback will call the `gtag()` function with `"config"` as the first argument, telling Analytics that there was a new page view. Like before, the second argument is the Analytics ID. The third argument is something a new: a config object with a `page_path` property.

The `gtag('config', ...)` function requires the `page_path` property in its configuration object to accurately track what page it's on when performing client-side navigation. Without `page_path`, Analytics will register it as another page view for the page that the user initially loaded. You can view all of the properties that the config object accepts in the [Analytics documentation](https://developers.google.com/analytics/devguides/collection/gtagjs/pages).

## Why not track _all_ page views from within the `turbolinks:loaded` callback?

You may be wondering why we don't just track all of our page views in the `turbolinks:loaded` callback. After all, it would simplify the code by removing the `gtag('config', ...)` call from the script we copied from the Analytics dashboard, and we would no longer have to check if it was the initial page load in the second script.

There's a good reason we want to separate these out. On a slow connection, it may take several seconds for the page to load and for Turbolinks to initialize. If a user is on a slow connection, waits for five seconds, and exits before Turbolinks loads, the `gtag('config', ...)` function would never fire because Turbolinks would have never loaded. By having the first `gtag()` function fire immediately when the page loads, it's much more likely that Analytics will capture the page view, even if the user bounces after a few seconds.

## Additional reading

Here are several resources I found helpful while figuring out how to hook this up. Maybe you'll find them valuable too.

* [Google Analytics Documentation: Single Page Application Measurement](https://developers.google.com/analytics/devguides/collection/analyticsjs/single-page-applications). This was written for the old analytics.js script, but the section at the end that discusses `document.referrer` and `document.location` seems like it is still relevant with gtag.js.

* [Optimize Smart: Sending pageview data via measurement protocol in Google Analytics](https://www.optimizesmart.com/sending-pageview-data-via-measurement-protocol-in-google-analytics/). This article demystifies the two letter abbreviations that the page is sending to Analytics on every request.