vTilt
Why vTiltHow It WorksFeaturesFAQDocs
Docs / Remix
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 guidesRemix

Remix

Integrate vTilt with Remix v2 — npm package or inline script, navigation pageviews, server-side events from loaders and actions.

Remix v2 is split into a server bundle and a client bundle. vTilt initialises in the browser and tracks Remix navigations explicitly. Server-side events go through the Node SDK from your loaders/actions.

Note

Note: Remix has been folded into React Router v7. New apps should follow the React Router guide.

#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 { RemixBrowser } from '@remix-run/react'
import { startTransition, StrictMode } from 'react'
import { hydrateRoot } from 'react-dom/client'
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,
  capture_pageleave: true,
})

startTransition(() => {
  hydrateRoot(
    document,
    <StrictMode>
      <RemixBrowser />
    </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. Track navigations

Remix uses client-side navigation between routes. Hook useLocation in your root and emit a pageview on every change.

// app/root.tsx
import { useEffect } from 'react'
import { useLocation } from '@remix-run/react'
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

Use a loader to expose the current user. The component identifies on every render where user is set — covering both fresh logins and visitors arriving with a session cookie.

// app/root.tsx (continued)
import { useLoaderData } from '@remix-run/react'

export async function loader({ request }) {
  const user = await getUserFromSession(request)
  return { user }
}

function Identify() {
  const { user } = useLoaderData<typeof loader>()
  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

For events from loaders and actions, install the Node SDK:

npm install @v-tilt/node
bash
// app/utils/vtilt.server.ts
import { VTiltNode } from '@v-tilt/node'

declare global {
  var __vtilt: VTiltNode | undefined
}
export const vtilt =
  global.__vtilt ??
  (global.__vtilt = new VTiltNode(process.env.VTILT_TRACKER_TOKEN!, {
    host: process.env.VTILT_HOST,
  }))
typescript
// app/routes/checkout.tsx
import { vtilt } from '~/utils/vtilt.server'

export async function action({ request }) {
  const form = await request.formData()
  vtilt.capture({
    distinctId: form.get('userId') as string,
    event: 'purchase_completed',
    properties: { source: 'remix-action' },
  })
  await vtilt.shutdown()
  return redirect('/thanks')
}
typescript

#Next steps

  • React Router — the modern successor to Remix.
  • Identify & alias — anonymous → known user merge.
  • Node SDK — server-side event shapes.
PreviousReact RouterIntegration guidesNextGatsbyIntegration guides

On this page

  • 1. Add your environment variables
  • 2. Install & initialise
  • 3. Track navigations
  • 4. Identify users
  • 5. Server-side events
  • Next steps