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.

app/components/PageViewTracker.tsx
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.

app/components/layout/ThemeLayout.tsx
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::changed is 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 Viewed once 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, call window.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.salla is undefined on the server and before the SDK is ready. Keep the code in useEffect and bind on theme::ready.
  • Consent: gate sending on your cookie banner if the store requires it.

Next

On this page