Skip to main content
For a client-routed React app built with Vite. Tested by the example examples/vite-react-spa (React 19, React Router 8 in data mode, Vite 8), which runs in the browser test matrix.

Where the tag goes

Put the tag in index.html, not in a component, so it loads once per page load:
The example replaces the comment with the tag at build time so that tests can use their own site id. In your app the tag from the console stands there:
Vite copies tags it does not process into the built index.html unchanged. Check dist/index.html after vite build once.

Route changes

The tracker sees every history.pushState and replaceState call and every back and forward navigation, so each route change of React Router (or any other router) is one pageview. Nothing needs to be added to the router.

Custom events

data-umami-event attributes work on any element React renders. For a call from code, declare window.umami once so TypeScript knows it:
and call it with optional chaining, because the visitor may block the tracker:

Hash routers

A router that keeps the route in the fragment (/#/settings) is counted too: fragments that start with #/ are sent without any query part inside them. Every other fragment is dropped, because a fragment can carry an OAuth token.

Bundling instead of a tag

To ship the tracker inside your bundle, see npm.md. The script tag is the simpler choice: its version and integrity are fixed in one visible place.

What the browser tests check

One pageview per route change, back and forward each counted, each pageview naming the previous route as referrer, one challenge per page load shared by all its events, both kinds of custom event, an external referrer kept on the first pageview, Global Privacy Control, and a tampered tracker refused (tests/e2e/specs).