Single Page Application Tracking

Single Page Applications (SPAs) present unique challenges for tracking because traditional pageview tracking relies on full page reloads. Learn how to implement proper tracking for SPAs built with React, Vue.js, Angular, and other modern frameworks.

The SPA Tracking Challenge

In traditional websites, each page navigation triggers a full page reload, making it easy to track pageviews. However, SPAs use client-side routing where:

  • Page content changes dynamically without full reloads
  • URLs change via JavaScript pushState/replaceState
  • Navigation happens instantly through JavaScript
  • Traditional tracking methods miss route changes
  • Virtual pageviews are not automatically captured

❌ Without SPA Tracking:

  • Only initial page load is tracked
  • Route changes are invisible to analytics
  • User journey appears incomplete
  • Conversion attribution is broken

✅ With Proper SPA Tracking:

  • All route changes are tracked as pageviews
  • Complete user journey is captured
  • Proper conversion attribution maintained
  • Accurate engagement metrics

Konektor SPA Implementation

1. Basic SPA Tracking Setup

Initialize Konektor with SPA mode enabled:

// Enable SPA tracking in initialization
konektor('init', 'WS-YOUR-WORKSPACE-ID', {
    spa: {
        enabled: true,
        autoTrack: true,  // Automatically track route changes
        trackHash: false  // Set to true if using hash routing
    }
});

2. Manual Route Change Tracking

For frameworks that need manual tracking, call the tracking function on route changes:

// Manual pageview tracking
function trackPageView() {
    konektor('track', 'pageview', {
        page: window.location.pathname,
        title: document.title,
        referrer: document.referrer
    });
}

// Call on every route change
trackPageView();

Framework-Specific Implementation

React with React Router

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function App() {
    const location = useLocation();

    useEffect(() => {
        // Track pageview on route change
        if (window.konektor) {
            window.konektor('track', 'pageview', {
                page: location.pathname,
                search: location.search,
                title: document.title
            });
        }
    }, [location]);

    return <YourApp />;
}

Vue.js with Vue Router

// In main.js or router.js
import router from './router';

router.afterEach((to, from) => {
    // Track pageview after route change
    if (window.konektor) {
        window.konektor('track', 'pageview', {
            page: to.path,
            title: to.meta?.title || document.title,
            referrer: from.path
        });
    }
});

Angular with Router

import { NavigationEnd, Router } from '@angular/router';
import { filter } from 'rxjs/operators';

export class AppComponent {
    constructor(private router: Router) {
        this.router.events
            .pipe(filter(event => event instanceof NavigationEnd))
            .subscribe((event: NavigationEnd) => {
                // Track pageview on navigation end
                if (window.konektor) {
                    window.konektor('track', 'pageview', {
                        page: event.url,
                        title: document.title,
                        referrer: event.urlAfterRedirects
                    });
                }
            });
    }
}

Next.js (Hybrid SPA/SSR)

// In _app.js
import { useRouter } from 'next/router';
import { useEffect } from 'react';

function MyApp({ Component, pageProps }) {
    const router = useRouter();

    useEffect(() => {
        const handleRouteChange = (url) => {
            // Track client-side route changes
            if (window.konektor) {
                window.konektor('track', 'pageview', {
                    page: url,
                    title: document.title
                });
            }
        };

        router.events.on('routeChangeComplete', handleRouteChange);
        return () => {
            router.events.off('routeChangeComplete', handleRouteChange);
        };
    }, [router.events]);

    return <Component {...pageProps} />;
}

Advanced SPA Tracking Features

Virtual Pageview Tracking

Track dynamic content changes as virtual pageviews:

// Track modal opens as virtual pages
function trackModalView(modalName) {
    konektor('track', 'pageview', {
        page: `/virtual/modal/${modalName}`,
        title: `Modal: ${modalName}`,
        virtual: true
    });
}

// Track tab changes
function trackTabChange(tabName) {
    konektor('track', 'pageview', {
        page: `${window.location.pathname}#${tabName}`,
        title: `${document.title} - ${tabName}`,
        virtual: true
    });
}

E-commerce SPA Tracking

For SPAs with e-commerce functionality:

// Track product views
function trackProductView(product) {
    konektor('track', 'view_item', {
        content_name: product.name,
        content_ids: [product.id],
        content_type: 'product',
        value: product.price,
        currency: 'IDR'
    });
}

// Track add to cart
function trackAddToCart(product, quantity) {
    konektor('track', 'add_to_cart', {
        content_name: product.name,
        content_ids: [product.id],
        content_type: 'product',
        value: product.price * quantity,
        currency: 'IDR',
        quantity: quantity
    });
}

User Authentication Tracking

Track login/logout events in SPAs:

// Track user login
function trackLogin(userId, method) {
    konektor('identify', userId);
    konektor('track', 'login', {
        method: method,
        user_id: userId
    });
}

// Track user logout
function trackLogout() {
    konektor('track', 'logout');
    konektor('reset'); // Clear user identity
}

Testing SPA Tracking

1. Manual Testing

  1. Navigate through your SPA using different routes
  2. Check browser network tab for tracking requests
  3. Verify events are sent to Konektor dashboard
  4. Test back/forward browser buttons
  5. Validate direct URL access tracking

2. Automated Testing

Create a testing script to verify SPA tracking:

// SPA Tracking Test Script
function testSPATracking() {
    const routes = ['/', '/about', '/products', '/contact'];

    routes.forEach(route => {
        // Simulate navigation
        window.history.pushState({}, '', route);

        // Trigger route change event
        window.dispatchEvent(new Event('popstate'));

        // Check if tracking was called
        console.log(`Testing route: ${route}`);
    });
}

testSPATracking();

3. Monitoring Tools

Use browser extensions to monitor tracking:

  • Konektor Debug Tool: Monitor tracking calls
  • Network Tab: Inspect tracking requests
  • Console Logs: Check for tracking errors

Common SPA Tracking Issues

Route Changes Not Tracked

Symptoms: Only initial page load tracked, route changes missed Solutions:

  • Ensure route change listeners are properly attached
  • Check that tracking calls are not blocked by errors
  • Verify framework-specific integration is correct

Duplicate Pageviews

Symptoms: Same page tracked multiple times Solutions:

  • Implement proper debouncing for route changes
  • Check for multiple route listeners
  • Use navigation guards to prevent duplicate tracking

Tracking Fires Too Early

Symptoms: Page content not loaded when tracking fires Solutions:

  • Delay tracking until content is ready
  • Use nextTick or setTimeout for Vue/React
  • Wait for DOM updates before tracking

History API Issues

Symptoms: Browser back/forward not tracked properly Solutions:

  • Listen to popstate events for browser navigation
  • Implement proper history change detection
  • Test all navigation scenarios

Performance Optimization

Efficient Tracking Implementation

// Debounced tracking to prevent spam
let trackingTimeout;
function debouncedTrack(event, data) {
    clearTimeout(trackingTimeout);
    trackingTimeout = setTimeout(() => {
        konektor('track', event, data);
    }, 100);
}

// Use in route changes
router.afterEach(() => {
    debouncedTrack('pageview', {
        page: window.location.pathname,
        title: document.title
    });
});

Lazy Loading Considerations

For SPAs with lazy-loaded routes:

// Track lazy-loaded components
const LazyComponent = lazy(() => import('./MyComponent'));

function App() {
    return (
        <Suspense fallback={<div>Loading...</div>}>
            <LazyComponent />
        </Suspense>
    );
}

// Track in lazy component
useEffect(() => {
    konektor('track', 'component_load', {
        component: 'MyComponent'
    });
}, []);

Best Practices

1. Consistent Implementation

  • Use the same tracking pattern across all routes
  • Maintain consistent event naming conventions
  • Standardize data structure for all events

2. Error Handling

  • Implement try-catch blocks around tracking calls
  • Gracefully handle cases where tracking fails
  • Log tracking errors for debugging

3. Privacy Compliance

  • Respect user consent for tracking
  • Implement proper data sanitization
  • Comply with privacy regulations (GDPR, CCPA)

4. Performance Monitoring

  • Monitor tracking call response times
  • Track tracking failure rates
  • Optimize bundle size impact

Framework Comparison

FrameworkIntegration MethodComplexityBest For
ReactuseEffect + router eventsMediumComponent-based apps
Vue.jsRouter navigation guardsEasyProgressive enhancement
AngularRouter events observableMediumEnterprise applications
Next.jsRouter eventsEasyHybrid SSR/SPA apps
Nuxt.jsRouter middlewareEasyVue ecosystem

Support Resources

Documentation

Getting Help

  • Technical Support: support@konektor.id
  • Framework Examples: GitHub repository
  • Community Forum: Discuss with other developers

:::info Pro Tip Start with basic route tracking, test thoroughly, then add virtual pageviews and advanced e-commerce tracking as needed. :::

:::warning Important SPA tracking requires testing all navigation scenarios - direct URL access, browser navigation, and programmatic routing. :::

Need More Help?

Our team is ready to help you maximize ad tracking and business attribution.

Email Supportsupport@konektor.id
YouTubeYouTube

© 2026 Konektor. All rights reserved.