Tracking pageviews in single-page apps (SPA)

Jun 11, 2024

A single-page application (or SPA) dynamically loads content for new pages using JavaScript instead of loading new pages from the server. Ideally, this enables users to navigate around the app without waiting for new pages to load, providing a seamless user experience.

PostHog's JavaScript Web SDK automatically captures pageview events on page load. The problem with SPAs is that page loads don't happen beyond the initial one. This means user navigation in your SPA isn't tracked.

To fix this, we have the ability to capture pageviews using the history API by setting the capture_pageview config option to history_change. This tutorial shows you how to do this for the most popular SPA frameworks like Next.js, Vue, Svelte, and Angular.

Prerequisite: Each of these requires you to have an app created and PostHog installed. To install the PostHog JavaScript Web SDK, run the following command for the package manager of your choice:

Terminal
yarn add posthog-js
# or
npm install --save posthog-js
# or
pnpm add posthog-js

Tracking pageviews in Next.js (app router)

To add PostHog to your Next.js app use it to capture pageviews, we create a PostHogProvider component in the app folder, initialize PostHog with our project API key and host (from your project settings), and set capture_pageview to history_change.

JavaScript
// app/providers.js
'use client'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
import { useEffect } from 'react'
export function PHProvider({ children }) {
useEffect(() => {
posthog.init('<ph_project_api_key>', {
api_host: 'https://us.i.posthog.com',
capture_pageview: 'history_change'
})
}, []);
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
}

We import this and use it in the app/layout.js file.

JavaScript
// app/layout.js
import "./globals.css";
import { PHProvider } from './providers'
export default function RootLayout({ children }) {
return (
<html lang="en">
<PHProvider>
<body>
{children}
</body>
</PHProvider>
</html>
);
}

Tracking pageviews in React Router v7

If you are using React Router, start by setting posthog-js and posthog-js/react as external packages in your vite.config.ts file.

vite.config.ts
// ... imports
export default defineConfig({
plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
ssr: {
noExternal: ['posthog-js', 'posthog-js/react']
}
});

Next, create a providers.tsx file in the app folder. In it, initialize the PostHog provider with capture_pageview set to 'history_change'.

TypeScript
// app/providers.tsx
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
if (typeof window !== 'undefined') {
posthog.init('<ph_project_api_key>', {
api_host: 'https://us.i.posthog.com',
capture_pageview: 'history_change',
})
}
export function PHProvider({ children }: { children: React.ReactNode }) {
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
}

Finally, import the PHProvider component in the app/root.tsx file.

TypeScript
// app/root.tsx
// ... imports
import { PHProvider } from "./provider";
// ... links, meta, etc.
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<PHProvider>
{children}
<ScrollRestoration />
<Scripts />
</PHProvider>
</body>
</html>
);
}
// ... rest of the file

Tracking pageviews in React Router (v6 or below)

If you are using React Router v6 or below AKA react-router-dom, start by adding the PostHogProvider component in the app folder. Make sure to set capture_pageview: false because we will manually capture pageviews.

JavaScript
// app/providers.js
'use client'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
if (typeof window !== 'undefined') {
posthog.init('<ph_project_api_key>', {
api_host: 'https://us.i.posthog.com',
capture_pageview: 'history_change'
})
}
export function PHProvider({ children }) {
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
}

We then import this and use it in the app/layout.js file.

JavaScript
import * as React from 'react';
import { PHProvider } from './providers'
function App() {
return (
<html lang="en">
<PHProvider>
<body>
{children}
</body>
</PHProvider>
</html>
);
}

Tracking pageviews in Vue

After creating a Vue app and setting up the vue-router, create a new folder in the src/components named plugins. In this folder, create a file named posthog.js. This is where we initialize PostHog with capture_pageview set to history_change.

JavaScript
// src/plugins/posthog.js
import posthog from "posthog-js";
export default {
install(app) {
app.config.globalProperties.$posthog = posthog.init(
"<ph_project_api_key>",
{
api_host: "https://us.i.posthog.com",
capture_pageview: 'history_change'
}
);
},
};

After this, you can add the plugin to the main.js file and use it along with the router to capture pageviews afterEach route change.

JavaScript
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import posthogPlugin from '../plugins/posthog';
const app = createApp(App);
app.use(posthogPlugin).use(router).mount('#app');

Tracking pageviews in Svelte

If you haven't already, start by creating a +layout.js file for your Svelte app in your src/routes folder. In it, add the code to initialize PostHog with capture_pageview set to history_change.

JavaScript
// src/routes/+layout.js
import posthog from 'posthog-js'
import { browser } from '$app/environment';
export const load = async () => {
if (browser) {
posthog.init(
'<ph_project_api_key>',
{
api_host: 'https://us.i.posthog.com',
capture_pageview: 'history_change'
}
)
}
return
};

Tracking pageviews in Angular

To start tracking pageviews in Angular, begin by initializing PostHog in src/main.ts with capture_pageview set to history_change.

JavaScript
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
import posthog from 'posthog-js';
posthog.init('<ph_project_api_key>',
{
api_host: 'https://us.i.posthog.com',
capture_pageview: 'history_change'
}
);
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));

Further reading

Subscribe to our newsletter

Product for Engineers

Read by 60,000+ founders and builders

We'll share your email with Substack

Questions? Ask Max AI.

It's easier than reading through 642 pages of documentation

Comments