React Router
Integrate vTilt with a React Router (formerly Remix) app — npm package or inline script, route-change pageviews, server-side events from loaders/actions.
React Router v7+ (the framework formerly known as Remix) supports both SSR and SPA modes. vTilt initialises on the browser side and tracks route changes; server-side events go through the Node SDK from loaders/actions.
#1. Add your environment variables
# .env
VITE_VTILT_TOKEN=YOUR_PROJECT_TOKEN
VITE_VTILT_HOST=https://www.vtilt.com
VTILT_TRACKER_TOKEN=YOUR_PROJECT_TOKEN#2. Install & initialise
Choose how you load the Browser SDK. npm initialises the SDK in entry.client.tsx; the inline script drops a stub in root.tsx with no package to install. Both expose the same window.vt global — the rest of this guide is identical either way.
Install the package:
npm install @v-tilt/browserInitialise in entry.client.tsx:
// app/entry.client.tsx
import { startTransition, StrictMode } from 'react'
import { hydrateRoot } from 'react-dom/client'
import { HydratedRouter } from 'react-router/dom'
import { vt } from '@v-tilt/browser'
vt.init(import.meta.env.VITE_VTILT_TOKEN, {
api_host: import.meta.env.VITE_VTILT_HOST,
autocapture: true,
capture_pageview: false, // emitted on every navigation below
capture_pageleave: true,
})
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<HydratedRouter />
</StrictMode>,
)
})#3. Capture pageviews on every navigation
Use a small root component that watches useLocation and emits a pageview when the path changes.
// app/root.tsx
import { useEffect } from 'react'
import { useLocation } from 'react-router'
import { vt } from '@v-tilt/browser'
function VTiltPageviews() {
const { pathname, search } = useLocation()
useEffect(() => {
vt.capture('$pageview', {
$current_url: window.location.href,
$pathname: pathname + search,
})
}, [pathname, search])
return null
}Render <VTiltPageviews /> inside <body>. Using the inline script? Replace vt with window.vt and drop the import.
#4. Identify users
Pull the current user from your loader and identify on the client. Re-runs on every navigation, so logged-in visitors are identified on every page load — not just at login.
// app/components/identify.tsx
import { useEffect } from 'react'
import { vt } from '@v-tilt/browser'
export function Identify({
user,
}: {
user: { id: string; email: string } | null
}) {
useEffect(() => {
if (user) vt.identify(user.id, { email: user.email })
}, [user])
return null
}See Identify & alias for the full model.
#5. Server-side events from loaders / actions
For server-side events, install the Node SDK:
npm install @v-tilt/node// app/utils/vtilt.server.ts
import { VTiltNode } from '@v-tilt/node'
let _client: VTiltNode | null = null
export function getVtilt() {
if (!_client) {
_client = new VTiltNode(process.env.VTILT_TRACKER_TOKEN!, {
host: process.env.VTILT_HOST,
})
}
return _client
}// app/routes/checkout.tsx
import { getVtilt } from '~/utils/vtilt.server'
export async function action({ request }: ActionFunctionArgs) {
const form = await request.formData()
const userId = form.get('userId') as string
const vtilt = getVtilt()
vtilt.capture({
distinctId: userId,
event: 'purchase_completed',
properties: { source: 'react-router-action' },
})
await vtilt.shutdown()
return redirect('/thanks')
}#Next steps
- Identify & alias — full identification model.
- Reverse proxy — block-resistant ingestion.
- Node SDK — server-side event shapes.