Single Page Application (SPA) Support
The tracker automatically detects navigation in Single Page Applications. No configuration required.
How It Works
The tracker intercepts:
history.pushState()- Triggered when navigating to a new URLhistory.replaceState()- Triggered when replacing current URLpopstateevent - Triggered when user clicks back/forward
Each URL change triggers a new pageview event automatically.
Supported Frameworks
Works out of the box with:
| Framework | Router | Tested |
|---|---|---|
| React | react-router v5/v6 | Yes |
| Vue | vue-router v3/v4 | Yes |
| Angular | @angular/router | Yes |
| Next.js | App Router, Pages Router | Yes |
| Nuxt.js | nuxt/router | Yes |
| Svelte | svelte-routing, SvelteKit | Yes |
| Remix | @remix-run/react | Yes |
Any router that uses the History API is automatically supported.
Installation
Same as regular installation:
<script src="https://t.sealmetrics.com/t.js?id=YOUR_ACCOUNT_ID" defer></script>
React Example
With react-router
// App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<Products />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="/checkout" element={<Checkout />} />
</Routes>
</BrowserRouter>
);
}
No additional setup needed. Navigation between routes is tracked automatically.
Tracking Conversions
// CheckoutSuccess.jsx
import { useEffect } from 'react';
function CheckoutSuccess({ order }) {
useEffect(() => {
// Pageview is automatic, just track conversion
if (typeof sealmetrics !== 'undefined') {
sealmetrics.conv('purchase', order.total, {
order_id: order.id,
currency: order.currency
});
}
}, [order]);
return <div>Thank you for your order!</div>;
}
With Content Grouping
// Use the group parameter in the script tag per page type,
// or set it dynamically:
function ProductPage({ product }) {
useEffect(() => {
if (typeof sealmetrics !== 'undefined') {
sealmetrics({ group: 'product' });
}
}, [product.id]);
return <div>{product.name}</div>;
}
Next.js Example
App Router (Next.js 13+)
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
<html>
<head>
<Script
src="https://t.sealmetrics.com/t.js?id=YOUR_ACCOUNT_ID"
strategy="afterInteractive"
/>
</head>
<body>{children}</body>
</html>
);
}
Pages Router
// pages/_app.tsx
import Script from 'next/script';
export default function MyApp({ Component, pageProps }) {
return (
<>
<Script
src="https://t.sealmetrics.com/t.js?id=YOUR_ACCOUNT_ID"
strategy="afterInteractive"
/>
<Component {...pageProps} />
</>
);
}
Dynamic Content Grouping
// app/blog/[slug]/page.tsx
'use client';
import { useEffect } from 'react';
export default function BlogPost({ params }) {
useEffect(() => {
if (typeof window !== 'undefined' && typeof sealmetrics !== 'undefined') {
sealmetrics({ group: 'blog' });
}
}, [params.slug]);
return <article>...</article>;
}
Vue Example
With vue-router
// main.js
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/products', component: Products },
{ path: '/products/:id', component: ProductDetail }
]
});
createApp(App).use(router).mount('#app');
Tracking Events in Components
<!-- ProductDetail.vue -->
<template>
<div>
<h1>{{ product.name }}</h1>
<button @click="addToCart">Add to Cart</button>
</div>
</template>
<script>
export default {
methods: {
addToCart() {
if (typeof sealmetrics !== 'undefined') {
sealmetrics.micro('add_to_cart', {
product_id: this.product.id,
product_name: this.product.name
});
}
// ... add to cart logic
}
}
};
</script>
Angular Example
Module Setup
// app.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'products', component: ProductsComponent },
{ path: 'products/:id', component: ProductDetailComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
Tracking Service
// analytics.service.ts
import { Injectable } from '@angular/core';
declare global {
interface Window {
sealmetrics: any;
}
}
@Injectable({ providedIn: 'root' })
export class AnalyticsService {
trackConversion(type: string, amount: number, properties?: object) {
if (typeof window.sealmetrics !== 'undefined') {
window.sealmetrics.conv(type, amount, properties);
}
}
trackMicro(type: string, properties?: object) {
if (typeof window.sealmetrics !== 'undefined') {
window.sealmetrics.micro(type, properties);
}
}
}
Nuxt.js Example
Nuxt 3
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
script: [
{
src: 'https://t.sealmetrics.com/t.js?id=YOUR_ACCOUNT_ID',
defer: true
}
]
}
}
});
Composable for Tracking
// composables/useAnalytics.ts
export function useAnalytics() {
const trackConversion = (type: string, amount: number, props?: object) => {
if (process.client && typeof sealmetrics !== 'undefined') {
sealmetrics.conv(type, amount, props);
}
};
const trackMicro = (type: string, props?: object) => {
if (process.client && typeof sealmetrics !== 'undefined') {
sealmetrics.micro(type, props);
}
};
return { trackConversion, trackMicro };
}
TypeScript Declarations
Add type declarations for better IDE support:
// types/sealmetrics.d.ts
interface SealmetricsOptions {
group?: string;
}
interface SealmetricsFunction {
(options?: SealmetricsOptions): void;
conv(type: string, amount: number, properties?: Record<string, string>): void;
micro(type: string, properties?: Record<string, string>): void;
}
declare global {
const sealmetrics: SealmetricsFunction;
const sm: SealmetricsFunction;
const _sm: SealmetricsFunction;
}
export {};
Troubleshooting
Duplicate Pageviews
If you see duplicate pageviews, check:
- Script is not included multiple times
- Not calling
sealmetrics()manually when automatic tracking is sufficient - No other analytics wrapper is re-triggering events
Missing Pageviews on Route Change
The tracker uses the History API. If your framework uses a custom navigation method that bypasses pushState/replaceState, pageviews won't be tracked.
Solution: Call sealmetrics() manually after navigation:
router.afterEach(() => {
if (typeof sealmetrics !== 'undefined') {
sealmetrics();
}
});
Hash-Based Routing
If your SPA uses hash-based routing (/#/path), the tracker will not automatically detect changes.
Solution: Listen for hashchange and track manually:
window.addEventListener('hashchange', function() {
if (typeof sealmetrics !== 'undefined') {
sealmetrics();
}
});
Server-Side Rendering (SSR)
Always check if sealmetrics exists before calling it:
if (typeof sealmetrics !== 'undefined') {
sealmetrics.conv('purchase', 99.99);
}
Or for Next.js/Nuxt:
if (typeof window !== 'undefined' && typeof sealmetrics !== 'undefined') {
sealmetrics.conv('purchase', 99.99);
}