Track page views
Send a page view on every navigation, including client-side ones.
Use this when you need one analytics view per page the customer sees, not one per full page load.
Goal: every page the customer opens sends a viewPage event to the data layer, once.
After the first load, links navigate in the browser without reloading, so a script that runs once misses them. After each client-side navigation the engine dispatches the SDK event route::changed with the current page (slug, id, title, url, ...). You subscribe once from a component in your layout. The useGtm hook pushes to dataLayer only when the store has a Tag Manager id. Background: Salla SDK and events, SEO and head.
Create a tracker component
It sends the first view on mount, then one per navigation, and ignores a repeat of the same URL.
import { useEffect, useRef } from 'react';
import { useGtm } from '@salla.sa/twilight-theme-engine/hooks/useGtm';
type Page = { slug?: string };
export function PageViewTracker() {
const gtm = useGtm();
const gtmRef = useRef(gtm);
gtmRef.current = gtm;
const last = useRef('');
useEffect(() => {
const send = (page?: Page) => {
const url = window.location.pathname + window.location.search;
if (url === last.current) return; // dedupe
last.current = url;
gtmRef.current.viewPage(page?.slug ?? window.location.pathname);
};
const listener = (...args: unknown[]) => send(args[0] as Page | undefined);
let bound = false;
const bind = () => {
const event = window.salla?.event;
if (bound || !event?.on) return;
event.on('route::changed', listener);
bound = true;
};
send();
bind();
document.addEventListener('theme::ready', bind);
return () => {
document.removeEventListener('theme::ready', bind);
if (bound) window.salla?.event?.off?.('route::changed', listener);
};
}, []);
return null;
}Render it once in your layout
Put it next to the other global pieces.
import { PageViewTracker } from '../PageViewTracker';
// ...inside the returned JSX of ThemeLayout:
<PageViewTracker />Check it worked
In the browser console run window.dataLayer (the store needs a Tag Manager id). Click through three pages: you see one viewPage entry per page, with no duplicates.
Traps
- A one-time
<script>or a mount effect alone sees the first page only. Later navigations never remount the layout. route::changedis a client-navigation event. The first view is not guaranteed to come from it, which is why the tracker sends once on mount and dedupes by URL.- Product views are already reported to the store. On a product page the engine dispatches the SDK event
Product Viewedonce per visit, which the store's analytics and pixels read. Do not dispatch it yourself; listen to it if your own tool needs product views. See Salla SDK and events. useGtm().viewPage(type)does nothing unless the store has a Tag Manager id. For other tools, callwindow.dataLayer.push(...)or your tool's SDK directly.- Render the tracker once, in the layout. In a page it adds a listener per page.
- Remove the listener with the same function reference, or it leaks.
window.sallaisundefinedon the server and before the SDK is ready. Keep the code inuseEffectand bind ontheme::ready.- Consent: gate sending on your cookie banner if the store requires it.
Next
- React to cart events: track add-to-cart the same way.
- Salla SDK and events: the event list.