vTilt
By roleFoundersKnow what to fix firstMarketersSee what happens after the clickCMOsSee what your budget really buys
By businessSaaSTurn signups into active usersEcommerceSee why shoppers abandon cartsAgenciesProve your work to clientsEnterpriseOne customer record for every team
MeasureWeb AnalyticsFind where buyers leaveSession ReplaySee why they left, not just that they didAdsKnow what paid visitors do nextIntegrationsOne snippet feeds your stack
ThinkPeopleOne profile per customerCustomer MemoryvTilt remembers every visitorSite KnowledgeAnswers and audits from your own pages
ActAsk AIAnswers from real behaviorAI ChatChat that knows the visitorEmail CampaignsEmail from real visits
PricingWhy vTiltDocs
Docs / React Router
Getting Started
OverviewInstallInitializeIdentify usersTrack eventsLogout & resetVerify eventsCommon mistakes
Guides
Event forwardingReverse proxyRealtime dashboardSite knowledge
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
API Reference
Browser SDK
Script bundlesAutocaptureWeb VitalsSession recordingChat widgetFeature readinessRemote configurationDebug logging
Node SDK
Install & setupCapture, identify & aliasContext & shutdownGlobal properties & opt-outError tracking
MCP server
Guides
OverviewAuthenticationOAuthAgent skills (prompts)AI intelligenceSite knowledgeGoogle AdsMeta Ads
Client setup
CursorClaude DesktopVS CodeCodex

Getting Started

OverviewInstallInitializeIdentify usersTrack eventsLogout & resetVerify eventsCommon mistakes

Guides

Event forwardingReverse proxyRealtime dashboardSite knowledge
Next.jsNuxt.jsVue.jsReactReact RouterRemixGatsbySvelte / SvelteKitAstroAngularTanStack StartDocusaurus

API Reference

MCP server

On this page

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
DocsGuidesFrontend frameworksReact Router

React Router

Last updated July 22, 2026

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

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.
  • Common mistakes — integration pitfalls to avoid before you ship.
PreviousReactIntegration guidesNextRemixIntegration guides