vTilt
Why vTiltHow It WorksFeaturesFAQDocs
Docs / React Router
Quick startEvent forwarding
MCP server
Guides
OverviewAuthenticationOAuthAgent skills (prompts)AI intelligenceGoogle Ads
Client setup
CursorClaude DesktopVS CodeCodex
Realtime
Debug ViewRealtime Dashboard
Integration guides
Frontend frameworks
Next.jsNuxt.jsVue.jsReactReact RouterRemixGatsbySvelte / SvelteKitAstroAngularTanStack StartDocusaurus
Backend frameworks
NestJSHonoCloudflare WorkersDjangoFlaskLaravelPhoenixRuby on Rails
Backend languages
PythonPHPRubyElixirGoJava.NET / C#Rust
Stack guides
Vue + PHP
SDK
Browser SDK
InstallScript bundlesEvent trackingAutocaptureIdentify & aliasWeb VitalsSession recordingChat widgetFeature readinessRemote configurationReverse proxyDebug logging
Node SDK
Install & setupCapture, identify & aliasContext & shutdown

Documentation

vTilt
Quick startEvent forwarding

MCP server

Realtime

Debug ViewRealtime Dashboard

Integration guides

Next.jsNuxt.jsVue.jsReactReact RouterRemixGatsbySvelte / SvelteKitAstroAngularTanStack StartDocusaurus

SDK

DocsIntegration guidesReact Router

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
text

#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/browser
bash

Initialise 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>,
  )
})
tsx

Tip

Tip: Need chat on first paint? Swap array.js for array.chat.js and add chat: { enabled: true }. See Script bundles.

#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
}
tsx

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
}
tsx

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
bash
// 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
}
typescript
// 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')
}
typescript

#Next steps

  • Identify & alias — full identification model.
  • Reverse proxy — block-resistant ingestion.
  • Node SDK — server-side event shapes.
PreviousReactIntegration guidesNextRemixIntegration guides

On this page

  • 1. Add your environment variables
  • 2. Install & initialise
  • 3. Capture pageviews on every navigation
  • 4. Identify users
  • 5. Server-side events from loaders / actions
  • Next steps