Clean GA4 for TanStack Router
A tiny, type-safe Google Analytics integration for TanStack Router and TanStack Start with automatic page views, typed GA4 helpers, and clean SSR-friendly setup.
Ben Houston • March 17, 2026 • 3 min read

Today I am releasing tanstack-router-ga4: a tiny Google Analytics integration for TanStack Router and TanStack Start with automatic page_view tracking, typed GA4 helpers, and a component you mount in a TanStack Start app shell.
Setup is one component in your root document:
import { HeadContent, Scripts } from '@tanstack/react-router'; import { GoogleAnalytics } from 'tanstack-router-ga4'; function RootDocument({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <head> <HeadContent /> </head> <body> <GoogleAnalytics measurementId="G-XXXXXXXXXX" /> {children} <Scripts /> </body> </html> ); }
Route changes now send GA4 page_view events.
Motivation#
I have used TanStack Router and TanStack Start across more projects over the last year. I like the explicitness and type safety of that stack, but analytics integration kept getting messy.
I kept hitting the same problems:
react-ga4did not work in my Vite 8 setup.- Many sample integrations online were generic React examples that missed TanStack Router's lifecycle.
- Hand-rolled route listeners are fragile and easy to get wrong.
- I kept seeing mistakes around multi-stream GA4 configuration.
- Many wrappers are either under-typed or not typed at all.
I did not want a giant analytics abstraction. I wanted a small module that handled the boring parts and stayed out of the way.
I packaged this as a reusable library because AI coding tools kept producing buggy GA4 integrations.
Out of the box, ChatGPT Codex, Claude Sonnet, and Gemini Pro kept generating GA4 code that looked plausible and failed on details that matter in production:
- hallucinated GA4 API capabilities that do not exist
- incorrect initialization order inside React apps
- confusion about
set()vsconfig() - ignored per-stream config
- broken assumptions about automatic page view behavior
- awkward client-only code that did not belong in TanStack Start root documents
I have lost time to this in production apps. A full analytics framework would be too much, but generated snippets keep missing GA4 details.
Why This Fits TanStack Router/Start Well#
TanStack Router and TanStack Start give you a real app shell and explicit route transitions. You can put analytics at the app shell instead of hiding it in route components.
Analytics is a cross-cutting concern. Instead of wiring up ad hoc listeners in random components, tanstack-router-ga4 lets you mount a GoogleAnalytics component near the top of your app and track route changes from there. That matches TanStack Start's app-shell model better than copy-pasted listeners.
The package is also type-safe. If I am using TanStack because I want stronger correctness guarantees, I do not want analytics to be the untyped corner of the codebase.
A Practical Example#
After you mount the component, you can use the hook anywhere you need to send events or update analytics state:
const ga = useGoogleAnalytics(); ga.set({ user_id: user.id, user_properties: { email: user.email, username: user.username, }, }); ga.event('sign_up', { method: 'email', });
Per-stream config works too:
const ga = useGoogleAnalytics(); ga.config('G-MAINSTREAM', { debug_mode: true, send_page_view: false, }); ga.config('G-MARKETING', { campaign_source: 'newsletter', send_page_view: false, });
Example snippets and generated integrations often get GA4's global and per-stream behavior wrong.
Try It#
pnpm add tanstack-router-ga4
- npm: tanstack-router-ga4
- GitHub: bhouston/tanstack-router-ga4
- Demo: tanstack-router-ga4.ben3d.ca
If your TanStack Router or TanStack Start app needs GA4, the package is on npm now.