vTilt
Why vTiltHow It WorksFeaturesFAQDocs
Docs / Next.js
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

DocsRealtimeNext.js

Next.js

Integrate vTilt with Next.js — App Router and Pages Router, npm package or inline script, client + server-side tracking with the Browser and Node SDKs.

vTilt works in any Next.js app (App Router or Pages Router) and across both runtimes. The Browser SDK handles client-side analytics, autocapture, and session recording; the Node SDK lets you emit events from server actions, route handlers, and middleware.

Tip

Tip: New to vTilt? Skim the Quick start first — this guide assumes you have a project token.

#1. Add your environment variables

Put your token and ingest origin in .env.local. Anything read in the browser must be prefixed NEXT_PUBLIC_.

NEXT_PUBLIC_VTILT_TOKEN=YOUR_PROJECT_TOKEN
NEXT_PUBLIC_VTILT_HOST=https://www.vtilt.com
VTILT_TRACKER_TOKEN=YOUR_PROJECT_TOKEN
text

#2. Install & initialise

Choose how you load the Browser SDK. npm gives you typed imports and bundling; the inline script drops a stub in your layout 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

Create a client component that calls vt.init() once, then render it in the root layout (App Router):

// app/providers/vtilt-provider.tsx
'use client'
import { useEffect } from 'react'
import { vt } from '@v-tilt/browser'

export function VTiltProvider() {
  useEffect(() => {
    vt.init(process.env.NEXT_PUBLIC_VTILT_TOKEN!, {
      api_host: process.env.NEXT_PUBLIC_VTILT_HOST,
      autocapture: true,
      capture_pageview: true,
      capture_pageleave: true,
    })
  }, [])
  return null
}
tsx
// app/layout.tsx
import { VTiltProvider } from './providers/vtilt-provider'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <VTiltProvider />
        {children}
      </body>
    </html>
  )
}
tsx

For the Pages Router, drop the same vt.init() into a useEffect in pages/_app.tsx instead.

Note

Note: App Router pages render on the server by default. vt.init() runs inside useEffect, which executes in the browser after hydration.

Tip

Tip: Need chat on first paint? Swap array.js for array.chat.js in the stub (or import the chat bundle on npm) and add chat: { enabled: true } to the init config. See Script bundles.

#3. Track route changes

App Router client navigation goes through the History API, which the SDK already patches (pushState / replaceState), so SPA pageviews fire automatically. If you set capture_pageview: false, emit them yourself from a usePathname() effect.

#4. Identify users

Call vt.identify(userId, properties) whenever the page loads for an authenticated user — not just on login. The most common integration bug is calling identify() only inside the login submit handler, which leaves returning logged-in visitors anonymous.

'use client'
import { useEffect } from 'react'
import { vt } from '@v-tilt/browser'

export function IdentifyOnMount({
  user,
}: {
  user: { id: string; email: string } | null
}) {
  useEffect(() => {
    if (user) vt.identify(user.id, { email: user.email })
  }, [user])
  return null
}
tsx

Note

Note: Using the inline script? vt is on window — drop the import (add declare const vt for TypeScript).

See Identify & alias for the full model.

#5. Server-side events with the Node SDK

For events from server actions, route handlers, and middleware, install the Node SDK:

npm install @v-tilt/node
bash

Construct one client per process and await shutdown() before serverless functions exit:

// app/api/checkout/route.ts
import { NextResponse } from 'next/server'
import { VTiltNode } from '@v-tilt/node'

const vtilt = new VTiltNode(process.env.VTILT_TRACKER_TOKEN!, {
  host: process.env.NEXT_PUBLIC_VTILT_HOST,
})

export async function POST(req: Request) {
  const { userId, amount } = await req.json()

  vtilt.capture({
    distinctId: userId,
    event: 'purchase_completed',
    properties: { amount, source: 'next-api' },
  })

  await vtilt.shutdown()
  return NextResponse.json({ ok: true })
}
typescript

Note

Note: On serverless platforms (Vercel, Netlify, Cloudflare), call await vtilt.shutdown() before each handler returns to flush events before the function freezes.

#Next steps

  • Identify & alias — the full identification model.
  • Reverse proxy — route SDK traffic through your own domain.
  • Node SDK / Capture, identify & alias — server-side event shapes.
  • Event forwarding — fan events out to GA4, Meta CAPI, PostHog.
PreviousRealtime DashboardRealtimeNextNuxt.jsIntegration guides

On this page

  • 1. Add your environment variables
  • 2. Install & initialise
  • 3. Track route changes
  • 4. Identify users
  • 5. Server-side events with the Node SDK
  • Next steps