Sift Docs
Frameworks

React SPA

Track a Vite, CRA, or React Router app, including client-side route changes.

For a single-page app, put the snippet in index.html. Sift picks up route changes from the History API, so you do not need a router integration.

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>My app</title>
    <script
      defer
      data-domain="app.example.com"
      src="https://sift.agentonboard.xyz/js/sift.js"
    ></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

What gets counted

  • The initial page load.
  • history.pushState calls — React Router, TanStack Router, and most other routers use these.
  • history.replaceState calls.
  • Back and forward navigation, through popstate.

Anchor links (#section) do not touch the History API, so they are not counted. A router that pushes a hash change through pushState does trigger a pageview, and it lands on the same pathname as the page before.

What does not get counted

A route rendered by replaceState during data loading may be replaced before the pageview is sent. The script compares the URL with the last one it sent, so repeated identical URLs produce one event and different URLs each produce one.

If you use a router that does not touch the History API — some hash routers — you will only see the initial pageview.

Keeping your own SPA navigation honest

Sift reads the URL, not your router state. If you render a "page" without changing the URL, it is the same pageview.

If you want a different path in the report than the browser URL, for example to strip an internal ID, that is not supported. Sift stores the pathname it sees.

Verify it

localhost is ignored. Test on a preview deployment or a tunnel, then open the site's install page in Sift and watch for Signal detected.

On this page