Single page applications (SPAs) are web apps or websites that load a single HTML page and dynamically update that page as the user interacts with it.
The most notable difference between a regular website and an SPA is the reduced amount of page refreshes. SPAs have a heavier usage of AJAX to get data. As a result, the process of rendering pages happens mostly on the client-side.

In traditional websites, when a user navigates from one page to another, the associated HTML, Javascript, CSS etc is rendered each time a new page loads. In SPAs, all of the necessary code is loaded once and changed when needed in response to user actions. The page does not reload during the entire user session. The URLs might change but that is to give the perception of logical pages.

The page does not reload during the entire user session

While SPAs are great for numerous reasons, they do require a little extra effort in order to set up client-side tracking than with a traditional webpage.

Include Google Analytics

This should go in the head of your root page (usually index.html) just before the closing </head> tag, it adds the ga object to the window object and dynamically loads the base google analytics script without the call to ga('create', ...).

<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', '//www.google-analytics.com/analytics.js', 'ga');
</script>

Initialise Google Analytics

When starting your app include the following line to initialise the Google analytics script:

window.ga('create', 'UA-XXXXXXXX-X', 'auto');

Tracking pageviews

After setting up our tracking code, we need to actually start tracking page views. Bellow I will give some solutions for Angular, React and Backbone.

In Angular.js

In your run function subscribe to the $stateChangeSuccess event of the UI Router and send a pageview with the current $location.path() to google analytics:

$rootScope.$on('$stateChangeSuccess', function (event) {
    $window.ga('send', 'pageview', $location.path());
});

Using Angulartics

As an alternative, you can use Angulartics which provides analytics plugins for AngularJS applications.
All you need to do is install it and include in your application.

$ bower install angulartics-google-analytics --save

Load the relevant files in your applications html (or include them in your build script).

<script src="/bower_components/angulartics/dist/angulartics.min.js"></script>
<script src="/bower_components/angulartics-google-analytics/dist/angulartics-ga.min.js"></script>

In your application dependencies, inject Angulartics and the Angulartics plugin for your vendor.

angular.module('myApp', ['angulartics', 'angulartics.google.analytics'])

Angulartics does automatic virtual pageview tracking by default.

In React.js

Assuming you're using React Router, adding pageview tracking is pretty simple. React Router uses the History API built into the browser to manipulate the URL, creating real URLs that look like example.com/some/path

Add the following to you application and the pageview event will be sent to Analytics whenever the location is changed:

history.listen((location) => {
  if (window.ga) {
    window.ga('send', 'pageview', location.pathname);
  }
});

Using react-ga

Much like Angulartics, there are similar libraries for React. Although there are others, I recommend using react-ga.

$ bower install react-ga --save

Load the relevant files in your applications html (or include them in your build script).

<!-- ReactGA library -->
<script src="/path/to/bower_components/react-ga/dist/react-ga.js"></script>

When included as a script tag, a variable ReactGA is exposed in the global scope. Initialize it in your application:

ReactGA.initialize('UA-000000-01', { debug: true });

In Backbone.js

Add the following code to your router:

initialize: function() {
  this.bind('route', this._pageView);
},

_pageView: function() {
  var path = Backbone.history.getFragment();
  ga('send', 'pageview', {page: "/" + path});
}

That's it!

A better way!

Wait, what? There is a better way? Why would I make you read all of this? Well... because I can.

Yes, there is a better way, a way to keep your application unaware about everything. No need for special libraries or ugly hacks.

We'll have to use Google Tag Manager for this one, but it's no rocket science. You need to create a new Trigger and a new Tag in order to track your virtual page views.

Create a New History Change Trigger

In the trigger area select New Trigger
New GTM trigger


Select History Change for the event. History change will fire whenever something like Backbone or React Router uses the history API. We’ll use this trigger later to fire Google Analytics.

New GTM trigger

Select to fire on All History Changes and save the Trigger.

Create an Analytics Tag

In the tags area select New Tag
GTM New Tag


Select Google Analytics as the tag product and universal analytics as the tag type. ![GTM Analytics Tag](/content/images/2016/12/Screen-Shot-2016-06-02-at-3.22.49-PM.png)
Configure the new tag with your analytics account’s UA code and track page views. ![Track page views](/content/images/2016/12/Screen-Shot-2016-06-02-at-3.20.04-PM.png)
Under *Fire On* select all pages and then click more. ![Fire On GTM](/content/images/2016/12/Screen-Shot-2016-06-02-at-3.25.35-PM.png)

We selected all pages to ensure the tag fires on initial load — the first time the user visits before your JavaScript router takes over and uses the history API to change the URL.

Clicking more will open a dialog with all your custom triggers. Select the history change trigger we created before and hit save.
Choose Trigger

That's it! After saving the tag, it's just a matter of time for the data to come through.

Resources: