Tagging Single Page Applications

Single Page Applications (SPA) are front-end-based web applications (HTML, JavaScript and CSS) that use a combination of AJAX and HTML5 features and the History State API to manage the URLs/routes, user interface, state and communication to the server.

Since a SPA does not require a page to be reloaded when the URL changes, a different approach to tag the application with the Facebook Pixel has to be followed.

When a user visits a typical website, the page is reloaded each on each visit. So when we implement the pixel, we can rely on putting the standard Facebook Pixel code in the header or footer with the corresponding events and send an event each time the page is reloaded.

The following outlines general approaches to tag your SPA site with the Facebook Pixel. It assumes that your application/framework uses the HTML5 History State API to manage URL changes/state as this is a best practice.

The general steps are:

  1. Initial Setup
  2. Tracking PageViews
  3. Tracking Specific/Key areas of the SPA using a Tag Manager such as Google Tag Manager or without a tag manager.

1. Initial Setup

On the initial Page Load, the FB Pixel JS code should be inserted into the page’s header/footer (or injected with a Tag Manager) as usual. The only potential modification to the code relates to how we track PageViews with the History State API listener feature in the pixel (next section).

2. Tracking PageViews

By default the Facebook Pixel has a HTML 5 History State API listener activated by default. This means that each time a new state is pushed into the history (e.g. history.pushState), it will fire a PageView event. It is important to note that due to the way the FB JS code was designed, you cannot fire more than one PageView event explicitly without the listenter. The recommended course of action is to leave the listener on and let it track the PageViews when the SPAs URL changes. This FB Pixel feature will also manage the referrers and URLs accordingly so Custom URL conversions can be implemented.

If for some reason, you do not want this default behaviour, it can be turned off using the flag disablePushState. Setting this flag to true will stop sending in PageView events on history state change.

Example of the usage below:

<script>
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
document,'script','https://connect.facebook.net/en_US/fbevents.js');

fbq.disablePushState = true; //not recommended, but can be done

fbq('init', '&lt;PIXEL_ID>');
fbq('track', 'PageView');
</script>

3. Tracking Specific/Key areas of the SPA

In many SPAs there are scenarios where key areas need to be tagged differently (beyond the simple PageView) for measurement, optimization and reporting reasons. For example, each steps of a registration form or key user behavior such as a Completed Registration/Lead or Purchase. As such, using one of the nine Standard Events are highly recommended to tag key areas/steps of your web applications.

The examples below is a simply jQuery application that mimics SPA behavior by implementing JS listeners and using the History State API (with and without a Tag Manager). The broad strategies should generally apply.

Based on the link the user clicks the following should happen:

  • User clicks link1.html: ViewContent Standard Event should fire
  • User clicks link2.html: AddPaymentInfo Standard Event should fire
  • User clicks link3.html: CompleteRegistration Standard Event should fire

Tracking without a Tag Manager

There is no one one-size fits all solution to this as it highly depends on the framework and the implementation details.

The general idea is to fire the Facebook event whenever there is a URL change in the SPA. Hooking it into the routing system of the framework or application is required.

In the following example:

  • A routing change is simulated when a link on the page is clicked (lines 74-77)
  • The loadContent function simulates an AJAX call to the server where new content is grabbed and a history.pushState function is called to change the URL of the SPA
  • The tracking event with the different events is called on line 63 - fbq('track' …. );

Example HTML File

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>SPA - Facebook Tracking - Simple Example</title>

  <!-- Facebook Pixel Code -->
  <script>
  !function(f,b,e,v,n,t,s)
  {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
  n.callMethod.apply(n,arguments):n.queue.push(arguments)};
  if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
  n.queue=[];t=b.createElement(e);t.async=!0;
  t.src=v;s=b.getElementsByTagName(e)[0];
  s.parentNode.insertBefore(t,s)}(window, document,'script',
  'https://connect.facebook.net/en_US/fbevents.js');
  fbq('init', '572317646281675');
  fbq('track', 'PageView');
  </script>
  <noscript>
  <img height="1" width="1" style="display:none"
  src="https://www.facebook.com/tr?id=572317646281675&ev=PageView&noscript=1"
  />
  </noscript>
  <!-- End Facebook Pixel Code -->

</head>

<body>
  <ul id="menu" class="clearfix">
    <li><a href="link1">Link 1</a></li>
    <li><a href="link2">Link 2</a></li>
    <li><a href="link3">Link 3</a></li>
  </ul>

  <hr/>

  <div id="content"></div>

  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"></script> 
  <script>
    (function($) {
      var loadContent = function(href) {
        $.ajax(href + ".html", {
          success: function(data) {
            history.pushState({ 'url': href }, 'New URL: ' + href, href);
            $('#content').html(data + new Date());

            //optional - just to demonstrate that additional events can be fired on particular path changes
            var eventname = null;
            switch (href) {
              case 'link1':
                eventname = 'ViewContent';
                break;
              case 'link2':
                eventname = 'AddPaymentInfo';
                break;
              case 'link3':
                eventname = 'CompleteRegistration';
                break;
              default:
            }
            fbq('track', eventname)


          },
          error: function() {
            console.log('An error occurred');
          }
        });
      };

      var init = function() {
        $('#menu a').click(function(e) {
          e.preventDefault();
          loadContent($(this).attr("href"));
        });
        
        // ensure back and forward buttons work
        window.onpopstate = function(event) {
          loadContent(location.pathname.split('/').pop());
        };
      };

      $(document).ready(function() {
        init();
      });
    })(jQuery);
  </script>
</body>
</html>

Tracking with a Tag Manager

Using a Tag Manager to tag a site is a common scenario that offers a maintainable/scalable solution to manage all tags and separating responsibilities (e.g. where many tagging items can be changed within a user-friendly interface as opposed to getting developers to make changes each time).

The following example uses Google Tag Manager (GTM) to demonstrate:

  • A routing change is simulated when a link on the page is clicked (lines 49-52)
  • PageView event is fired by the FB JS Pixel code on each URL change. This is the default behaviour
  • The Standard Events are set and triggered from within GTM

Example HTML File

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>SPA - Facebook Tracking - Simple Example</title>

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-PN4KG77');</script>
<!-- End Google Tag Manager -->

</head>

<body>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PN4KG77"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

  <ul id="menu" class="clearfix">
    <li><a href="link1">Link 1</a></li>
    <li><a href="link2">Link 2</a></li>
    <li><a href="link3">Link 3</a></li>
  </ul>

  <hr/>

  <div id="content"></div>

  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"></script> 
  <script>
    (function($) {
      var loadContent = function(href) {
        $.ajax(href + ".html", {
          success: function(data) {
            history.pushState({ 'url': href }, 'New URL: ' + href, href);
            $('#content').html(data + new Date());
          },
          error: function() {
            console.log('An error occurred');
          }
        });
      };

      var init = function() {
        $('#menu a').click(function(e) {
          e.preventDefault();
          loadContent($(this).attr("href"));
        });
        
        // ensure back and forward buttons work
        window.onpopstate = function(event) {
          loadContent(location.pathname.split('/').pop());
        };
      };

      $(document).ready(function() {
        init();
      });
    })(jQuery);
  </script>
</body>
</html>

Setting Up GTM requires:

  • Putting the GTM code on the webpage
  • Creating each tag within GTM
  • Creating a Listener for the History State API changes
  • Creating a trigger for each particular page to fire the FB Pixel event

Creating the Tags in Google Tag Manager:

The tag is the snippet of code to fire the Standard Event that is needed. In the following screenshot it shows the CompleteRegistration event being set up to fire. In more complex examples, you can also incorporate dynamic variables from the Data Layer.

Creating a Listener for the History State API changes:

A general listener (“History Change”) needs to be set up to ensure GTM listens and fires a gtm.historyChange event when it detects a push state change (URL change in the application).

The push/pop event is also fired when a user clicks the back/foward button, so if there is the ability for a user to move ahead and backwards in the flow, events can fire multiple times in this fashion depending on how the triggers are set up.

Creating a Trigger to fire the Tag:

A “Custom Event” trigger needs to be set in conjunction with the previous history state listener (the step above) to fire the tag.

Final View of Triggers

Debug

Using dataLayer plugins as well as the Network Tab in Chrome Developer tools (or using the Facebook Pixel Helper Chrome Extension) we can see the events firing as desired.

Appendix

What is a SPA?

Single-Page Applications (SPAs) are Web apps that load a single HTML page and dynamically update that page as the user interacts with the app. SPAs use AJAX and HTML5 to create fluid and responsive Web apps, without constant page reloads.

The following diagram describes the fundamental difference in the way that SPAs and traditional web application requests happen where SPA applications do not initiate a page refresh/reload.

Why create a SPA?

SPAs are becoming more prevalent these days for a variety of business and technical reasons. For example, depending on technical resources available, application infrastructure choices/state and considerations around maintainability and performance. Writing a SPA application to deploy an interface of a web application may be the right choice given certain situations.

How can I create a SPA?

Popular JavaScript (JS) frameworks also facilitate the creation of SPAs by providing reusable building blocks (e.g. views, routing systems, design patterns to separate responsibilities etc.) to efficiently create these types of applications, such as:

  • Angular.js
  • Backbone.js
  • Ember.js
  • Knockout.js
  • Meteor.js
  • React.js

Using one of these frameworks is not mandatory, as the ability apply the patterns around SPAs relates to the ability to use AJAX and HTML 5 features. So for example, you could natively implement one (pure vanilla JS), or even cobble together an application with jQuery and several plugins.

Sources:

MSDN Magazine