Data engineeringJanuary 26, 2021

Track User Events in Single-Page Applications

Owing to their fast load times and smooth user experiences, Single-Page Applications (SPAs) are now an extremely popular design pattern for developing websites. While building your site as an SPA offers clear advantages for your customers, it places challenges in the way of collecting robust analytics on user behavior.

Many organizations are now building their websites as single-page applications (SPAs)––a particular pattern of requesting and delivering content to a browser that does not involve repeated page refreshes to display new elements to the user. SPAs offer advantages over “traditional” websites including decreased page load times and smoother transitions, and they help enable websites to more closely emulate the experience of native applications––something users have now come to expect. 

While engineers can debate the pros and cons of SPAs, one thing is for certain––SPAs are now becoming the gold standard for user experiences on the web. Some of today’s most robust web apps like Netflix, Facebook, Google Maps, and Amazon are based on SPA architecture. The transition to SPAs has also helped put wind in the sails of massively popular JavaScript frameworks like React, Angular, and Vue among others, all of which help developers build sites that leverage this pattern.

Multi-page applications: The “traditional” way of serving websites

In “traditional” websites, loading a new page requires completing a full request-response cycle between the client and the server. This means that whenever the user clicks a link, the browser sends an HTTP GET request to the server for the content the user has requested. The server then interprets that request, and if it is valid, sends all of the resources required to view the content the user has requested, which usually includes HTML, CSS, and JavaScript files. Even if some of the content the user has requested is already present in the browser, the server still has to send the entire page and all associated resources to the client in order to complete the request. 

How SPAs diverge from multi-page applications

The main difference between SPAs and “traditional” (or “multi-page”) websites has to do with how pages are loaded and new content is served to the user. In an SPA, the browser loads all of the resources it needs to deliver the full experience of the site on the first page load. After that, when the user navigates to a different page or requests resources that are not yet in the UI, the new resources are dynamically requested and loaded and the rest of the page stays the same.

By leveraging HTML5 APIs, AJAX, and the capabilities of modern browsers, SPAs shift the workload of site navigation from the server to the browser. This is especially useful in interfaces that repeatedly display new content as a user scrolls down a feed. Examples of such use cases in our day-to-day experience of the web are plentiful––think scrolling through new Tweets, looking through product results on Amazon, or checking your email. 

Load time is the main advantage that SPAs offer over traditional sites, and this alone is a significant reason to choose SPA architecture for your application. It is now well understood that slower load times have a direct negative impact on site user retention and sales. In a research on page load times, for example, Google found that as page loads go from one to three seconds, bounce rates increase 32% on average. When load times increase by five seconds, a staggering 90% increase in bounce rate can be expected. 

SPAs require new event tracking strategies

With all their advantages in terms of user experience, SPAs do come with drawbacks. For one, moving navigational logic to the client means that browsers need to ingest far more data on initial page load to be able to dynamically display UI changes. This means that less modern devices and browsers have a harder time loading and serving content than newer ones. Also, certain accessible features of the browser can break when default behaviors relating to navigation are overridden. This post goes into more detail on strategies for addressing accessibility concerns in SPAs.

Another area in which SPAs present developers with challenges is collecting robust analytics on onsite behavior and user journeys. With a detailed picture of how users engage with your website, you can more easily optimize user flows, and remove blockers standing in the way of common actions like product searches and checkout. Traditional ways of collecting and tracking events and other user data with JavaScript snippets and links that fire in the head of the HTML document are obsolete in SPAs. Since the browser technically only needs to load the page content once, these snippets would only be triggered on the initial rendering of the page. Therefore, in the context of an SPA, traditional <script> or <link> tracking tags have very limited utility. 

Although you cannot rely on traditional methods like onload to trigger your user event tracking logic in an SPA, there are other strategies you can implement to ensure that yours apps are still collecting analytics across your customer journeys. As a general rule, with the exception of tracking snippets that only need to fire at the beginning of a page session, all functions that track user events should be decoupled from the normal page loading lifecycle. A good workaround for this is to use DOM event listeners like click, mouseover, mouseout or scroll to trigger event tracking functions. 

Send user event data from a React app to your mParticle dashboard

Let’s take a close look at how to implement a commerce event in a React application. In this scenario, when users click on furniture products in the UI to expand their photos, we want to log the product details and send them to mParticle––a customer data platform integrates all of your data and orchestrates it across channels, partners, and systems.

The mParticle web SDK is a stateful HTTP client wrapped into the Events API. It allows developers to track many different types of user events in a web UI and send them to mParticle in the absence of a request-response cycle between the client and the browser. Without needing to write any code, the web SDK automatically logs the start and end times of user website sessions and the user’s device ID. It also includes pre-built methods for tracking several different commerce events, consent management and error tracking

Since these user actions will not cause the page to re-render, we will need to set up a DOM event listener that captures which product the user selects, dynamically create a data object containing information about the selected product, then send this object to mParticle. 

Here is a workflow to put this event collection in place: 

The first step is to create a utility function in our React application. In analytics.js, we define a ViewDetail function that takes advantage of mParticle’s commerce tracking set-up (part of the Events API) to accomplish three main things: 

  1. Take in a product object with all of the relevant details about the item the customer has viewed.
  2. Create a representation of the product being viewed with the built-in eCommerce.createProduct() method.
  3. Send this product to mParticle as a product action.

The ViewDetail function looks like this:

export function ViewDetail(product) {
  const mParticle = window.mParticle;
  // Create a product object
  const productDetails = mParticle.eCommerce.createProduct(
    product.name,
    product.sku,
    product.price,
    product.quantity
  );
  // Send details of viewed product to mParticle
  mParticle.eCommerce.logProductAction(mParticle.ProductActionType.ViewDetail, productDetails)
}

Next, we import our ViewDetail function into our main App component, where we pass it as props to the Product component. There, we’ll call as an onClick function to fire when the user clicks on a product:

App.js

function App() {
  console.log(window.mParticle)
  return (
    <div className="App">
      <h1 className='main-heading'>
        Living room furniture
      </h1>
      <div className="product-image-container">
      {Object.keys(products).map(product => {        
        return (<Product 
        backgroundImage={products[product].image}
        product={products[product]}
        ViewDetail={ViewDetail}/>)
      })}     
      </div>
      <div className="button-container">
        <button className="register">proceed to checkout</button>
      </div>
    </div>
  );
}

Product.js

export default function Product(props) {
  const style = {backgroundImage: `url(${props.backgroundImage})`}  
  return (
    <div className="product-image" id="product1" style={style} onClick={() => props.ViewDetail(props.product)}>      
    </div>
  );
}

Notice that we are passing props.product to the ViewDetail function in Products.js––this comes from an external module containing the name, price, SKU and quantity in store for each product. Now when the user clicks on one of these products, all of this data will be forwarded to the mParticle dashboard along with the device ID and user’s location, which are included by default. 

In this particular example, we use onClick to fire the utility function that sends data to mParticle, but we could use plenty of other triggers within React to log other user actions. If we want to keep track of page views, we could use a lifecycle method like componentDidMount to call a utility function, for example. 

Feel free to clone this example and experiment with different methods of collecting and tracking user event data in a React application. With the flexibility of mParticle’s Events API and the plethora of ways you can fire custom event trackers in a React component, you can paint a very detailed picture of how customers are using your web application.

For more on our web SDK, you can explore the documentation here.

Get started with mParticle today

Connect with an mParticle expert to discuss how to integrate and orchestrate customer data the right way for your business.

Request a demo

Startups can now receive up to one year of complimentary access to mParticle. Learn more