Skip to main content
In a server-rendered Shopify theme, every product or collection page is a full page load — DOMContentLoaded is a reliable trigger for product_view and collection_view events. In a single-page app (Hydrogen, Next.js Commerce, Remix, custom React/Vue storefronts), navigation is client-side and DOMContentLoaded only fires once. You need to hook into the router instead. This guide assumes you’ve already set up the enqueue() helper and session/attribution helpers from Sending events with navigator.sendBeacon.

Prerequisites

  • A working enqueue() function and a flush-on-pagehide listener.
  • getSessionId() and getAttributionToken() helpers.
  • The Layers storefront access token available to your client bundle (typically via a build-time env var like NEXT_PUBLIC_LAYERS_TOKEN).

React Router / Next.js App Router

Use a top-level effect that re-runs when the pathname changes.
Drop <LayersTracker product={...} /> into your product page layout and <LayersTracker collection={...} /> into your collection page layout. The effect dependency array re-fires the event when the route changes between two products or two collections.

Remix / React Router v6

Hook into useLocation() the same way.

Vue Router

Subscribe to the global afterEach guard.
Set meta.layers on each route when you define it:

Capturing the attribution token after every Search / Browse / Blocks call

The attribution token rotates per request. Update it from your data fetcher so subsequent clicks and conversions carry the right one:
If your storefront fetches Search, Browse, and Blocks data on the server (e.g., Next.js server components, Remix loaders), forward the attributionToken to the client via the page’s props or a small <script> tag so the tracker can pick it up.

Why pagehide still matters in a SPA

Even though your app doesn’t do full page reloads on internal navigation, the browser still fires pagehide when the user:
  • Navigates to an external site.
  • Closes the tab or window.
  • Goes to the back/forward cache on mobile Safari.
The unload-flush listener you set up in the navigator.sendBeacon guide handles all of these. Keep it.

Next steps