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.
#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 { 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>,
)
})#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
}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
}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// 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,
}))// 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')
}#Next steps
- React Router — the modern successor to Remix.
- Identify & alias — anonymous → known user merge.
- Node SDK — server-side event shapes.