Event tracking on Single Page Applications (SPA)

This documentation guides developers through integrating a custom event tracking system within React/Next.js applications. It focuses on capturing and analyzing user interactions to gain insights into application usage and behavior.

How SPAs different from MPAs

Multi-Page Applications (MPAs)

In traditional Multi-Page Applications, each user action that requires a new data view leads to a new request to the server, which then responds with a new HTML page. This process requires a full page reload, including re-fetching resources such as HTML, CSS, and JavaScript, which can lead to significant page loading speed and a user experience. The architecture is straightforward, with each page representing a separate document, making it easier to design for SEO but potentially slower in user interaction responsiveness.

Key characteristics:

  • Each page corresponds to a new URL.
  • Server-side rendering of pages.
  • Full page reloads for fetching new content.
  • Clear SEO advantage due to distinct pages.

Single Page Applications (SPAs)

SPAs, on the other hand, load a single HTML page and dynamically update that page's content with JavaScript as the user interacts with the app. This model avoids page reloads by fetching only the necessary data in response to user actions, and then rendering the new view in the browser. This approach significantly improves the user experience with smoother transitions and faster interactions. However, it introduces complexity in state management, routing, and SEO optimisation.

Key characteristics:

  • Single HTML page load with dynamic content updates.
  • Client-side rendering with JavaScript handling the UI updates.
  • Reduced server requests.
  • Complex state management and routing.
  • SEO optimisation requires additional strategies like server-side rendering or pre-rendering.

Divergence Points

  • MPAs reload the entire page from the server on most user actions, while SPAs reload only data, updating the UI dynamically
  • In MPAs, the server renders pages, sending complete HTML to the browser. SPAs handle rendering in the browser, fetching data via APIs, and using JavaScript to build page content.
  • SPAs offer a more app-like, with more fancy feedback and transitions, whereas MPAs might feel slower due to full-page reloads.
  • MPAs have a straightforward SEO setup with distinct URLs for each page. SPAs require additional tools and configurations for SEO and maintaining the app's different states.

Event-tracking strategies for SPAs

A specific event-tracking approach is crucial for single-page applications (SPAs) due to their dynamic nature and the absence of traditional page reloads. Here's a straightforward approach to event tracking in SPAs.

  • Set up event listeners for user actions like clicks and scrolls. This lets you capture events even without page reloads
    document.addEventListener('click', function(event) {
      if (event.target.matches('.trackable')) {
        sendEvent('click', {});
      }
    });
    
  • Use the History API to track page views without actual page reloads. Monitor changes in the history state to log these virtual page views
    window.onpopstate = function(event) {
      sendEvent('page_view', {pathname: window.location.pathname});
    };
    
  • Make sure your tracking script is set up once, ideally in the main entry file of your SPA, because it doesn't need to reload with each view change.
  • Take advantage of tools and libraries that come with your SPA framework (like React, Angular, Vue, etc.) which are designed for dynamic content and can make event tracking easier.
  • Make sure your tracking doesn't negatively affect the SPA's accessibility or speed. Use efficient methods that don't block other processes for tracking.

Step-by-Step Guide for Event Tracking in SPAs with React/Next.js

Getting Started

Before integrating event tracking, ensure your React/Next.js environment is set up and you're familiar with React hooks, as these will be used for tracking user events and managing state. Our goal here is to implement a comprehensive event tracking system that captures critical user interactions within your application. We'll focus on the following key objectives with real-world examples:

  • Tracking user page visits
  • Tracking button clicks
  • Tracking login form submissions
  • Tracking user sign out

Step 1: Including the Intempt Tracking Library

  1. Open or create the pages/_app.js file in your Next.js project.

  2. Import the Head component from 'next/head'.

  3. Include the Intempt tracking script in the Head section of your application, as shown below:

    import Head from 'next/head';
    
    function MyApp({ Component, pageProps }) {
      return (
        <>
          <Head>
            <script
              src="https://cdn.intempt.com/intempt.min.js"
              async
              data-organization={process.env.NEXT_PUBLIC_INTEMPT_ORG_NAME}
              data-project={process.env.NEXT_PUBLIC_INTEMPT_PROJECT_NAME}
              data-source={process.env.NEXT_PUBLIC_INTEMPT_SOURCE_ID}
              data-key={process.env.NEXT_PUBLIC_INTEMPT_API_KEY}
            ></script>
          </Head>
          <Component {...pageProps} />
        </>
      );
    }
    
    export default MyApp;
    
  4. Replace the environment variable placeholders with your actual Intempt project configuration. For more information on script installation, visit the Intempt JavaScript SDK Installation Guide.

Step 2: Create a Custom Hook for Event Tracking

  1. Create a new file for your custom hook, e.g., hooks/useTrackingEvent.js.

  2. Implement the hook using useCallback to wrap your event tracking logic:

    import { useCallback } from 'react';
    
    export const useTrackingEvent = () => {
      const trackEvent = useCallback((eventName, eventBody) => {
        window.intempt.recordEvent(eventName, eventBody);
      }, []);
    
      return trackEvent;
    };
    

Step 3: Let's Track User Page Visits

We aim to track every instance a user navigates to the Dashboard screen within our application. We can understand how often users engage with the Dashboard, a central Intempt activity hub, by monitoring these visits.

  1. Determine the route or path that corresponds to the Dashboard screen in your application. For example, it might be /dashboard in your routing setup

  2. In the Dashboard component file, import newly created tracking hook:

    import { useEffect } from 'react';
    import { useTrackingEvent } from '../hooks/useTrackingEvent';
    ...
    
  3. Utilize the useEffect hook to trigger the tracking event when the component mounts, indicating the user has navigated to the Dashboard:

    const Dashboard = () => {
      const trackEvent = useTrackingEvent();
    
      useEffect(() => {
        trackEvent('page_visit', { 
          page: 'Dashboard',
          // Identify the user's role if applicable (e.g., 'admin', 'user')
          userRole: getCurrentUserRole(), 
          // Capture the referrer to understand where users are coming from
          entryPoint: document.referrer, 
        });
      }, [trackEvent]);
    
      return (
        // Dashboard component JSX
      );
    };
    

Step 4: Let's Track "Add To-Do Item" Button Clicks

We aim to track each click on our application's "Add" button to add new to-do items. This action is a crucial interaction within the task management feature, providing insights into how frequently users engage with task creation.

  1. Locate the button element in your component that's responsible for adding new to-do items.

    const TodoComponent = () => {
      return (
        <button>Add</button>
      );
    };
    
  2. In your to-do component file, ensure you have imported the tracking hook or analytics library you're using for event tracking.

    import { useTrackingEvent } from '../hooks/useTrackingEvent';
    
    const TodoComponent = () => {
      return (
        <button>Add</button>
      );
    };
    
  3. To capture the details of the to-do item, including its text and expiration date, set up state variables.

    import React, { useState } from 'react';
    import { useTrackingEvent } from '../hooks/useTrackingEvent';
    
    const TodoComponent = () => {
      const [todoText, setTodoText] = useState('');
      const [expirationDate, setExpirationDate] = useState('');
      
      return (
        <button>Add</button>
      );
    };
    
  4. Create a function that will handle the logic when the "Add" button is clicked. This function should perform the addition of the to-do item and track the event.

    import React, { useState } from 'react';
    import { useTrackingEvent } from '../hooks/useTrackingEvent';
    
    const TodoComponent = () => {
      const trackEvent = useTrackingEvent();
      const [todoText, setTodoText] = useState('');
      const [expirationDate, setExpirationDate] = useState('');
      
      const handleAddClick = () => {
        // Add todo logic here (e.g., updating state, sending to backend)
    
        // Track the add button click with additional details
        trackEvent('todo', { 
          // Include the text of the todo item
          todoText: todoText, 
          // Include the expiration date of the todo item
          expirationDate: expirationDate 
        });
    
        // Clear the input fields after adding the todo item
        setTodoText('');
        setExpirationDate('');
      };
      
      return (
        <button>Add</button>
      );
    };
    
  5. Build the UI for your component, including input fields for the to-do text and expiration date, and the "Add" button with the handleAddClick event handler. Add an event listener to the "Add" button that triggers your tracking function when the button is clicked.

    import React, { useState } from 'react';
    import { useTrackingEvent } from '../hooks/useTrackingEvent';
    
    const TodoComponent = () => {
      const trackEvent = useTrackingEvent();
      const [todoText, setTodoText] = useState('');
      const [expirationDate, setExpirationDate] = useState('');
    
      const handleAddClick = () => {
        // Assuming a function 'addTodo' adds the todo item to your list or backend
        addTodo({
          text: todoText,
          expires: expirationDate
        });
    
        // Track the add button click with additional details
        trackEvent('todo', {
          // Include the text of the todo item
          todoText: todoText, 
          // Include the expiration date of the todo item
          expirationDate: expirationDate 
        });
    
        // Clear the input fields after adding the todo item
        setTodoText('');
        setExpirationDate('');
      };
    
      return (
        <div>
          <input
            type="text"
            placeholder="Enter todo..."
            value={todoText}
            onChange={(e) => setTodoText(e.target.value)}
          />
          <input
            type="date"
            value={expirationDate}
            onChange={(e) => setExpirationDate(e.target.value)}
          />
          <button onClick={handleAddClick}>
            Add
          </button>
        </div>
      );
    };
    

Step 5: Tracking Login Form Submissions

The primary goal is to track every submission of the login form within our application. This tracking is vital for understanding how users interact with the login process, identifying any issues or delays they might encounter, and ensuring a seamless login experience.

  1. Determine the form or component in your application that handles user logins. This form will typically have input fields for username/email and password, and a submit button.

    const LoginForm = () => {
        const handleSubmit = (event) => {
            event.preventDefault();
        };
    
        return (
            <form onSubmit={handleSubmit}>
                <input type="email" placeholder="Email" />
                <input type="password" placeholder="Password" />
                <button type="submit">Log In</button>
            </form>
        );
    };
    
  2. Import your analytics hook into the login component. Adjust the form's onSubmit function to incorporate both the tracking of the submission event and the user identification through their email.

    import { useTrackingEvent } from '../hooks/useTrackingEvent';
    
    const LoginForm = () => {
        const trackEvent = useTrackingEvent();
    
        const handleSubmit = async (event) => {
            event.preventDefault();
          
          	// Assuming the email field's name is 'email'
            const email = event.target.email.value; 
    
            // Identify the user for tracking purposes
            window.intempt.identifyUser(email);
    
            // Track the form submission event
            trackEvent('form_submission', { 
              // Form type
              form: 'Login',
              // Indicate whether the login was successful
              success: true, 
              // Specify the login method if multiple options are available (e.g., email, social media)
              loginMethod: 'email',
              // If the login attempt fails, specify the error type (e.g., 'invalid_credentials'). Make sure this does not include sensitive information.
              error: ''
            });      
        };
    
        return (
            <form onSubmit={handleSubmit}>
                <input name="email" type="email" placeholder="Email" required />
                <input name="password" type="password" placeholder="Password" required />
                <button type="submit">Log In</button>
            </form>
        );
    };
    

Step 5: Tracking User Sign-Out Events

The goal is to accurately log every instance when a user signs out of the application. This tracking provides valuable data on session durations and how users interact with the application over time.

  1. Locate the part of your application that handles user sign-outs. This could be a sign-out button in the navigation bar or within a user profile menu.

  2. Modify the sign-out function to include the tracking call.

    import { useTrackingEvent } from '../hooks/useTrackingEvent';
    
    const SignOutButton = () => {
        const trackEvent = useTrackingEvent();
    
        const handleSignOut = async () => {
            // Track the sign-out event
            trackEvent('user_action', { 
              // Action type
              action: 'Sign Out',
              // Include the reason for sign-out (e.g., 'user_initiated', 'timeout', 'error')
              reason: 'user_initiated' 
            });
    
            // End the user session
            window.intempt.endSession();
    
            // Your sign-out logic here
        };
    
        return (
            <button onClick={handleSignOut}>Sign Out</button>
        );
    };
    

Considerations

  • Test the event tracking in your application to ensure the tracking event is captured every time. Confirm the events show up in your Intempt analytics dashboard.
  • If the button can be clicked rapidly in succession, consider implementing throttling or debouncing to avoid flooding your analytics with too many events.

Summary

This guide provides a structured approach to tracking user interactions within Single Page Applications (SPAs). By implementing these tracking strategies, developers can gain deep insights into user behavior, session lengths, and engagement patterns, which are essential for optimising user experience and application performance.