vTilt
Why vTiltHow It WorksFeaturesFAQDocs
Docs / Cloudflare Workers
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

NestJSHonoCloudflare WorkersDjangoFlaskLaravelPhoenixRuby on Rails

SDK

DocsIntegration guidesCloudflare Workers

Cloudflare Workers

Integrate vTilt with Cloudflare Workers — Node SDK on the workerd runtime, waitUntil() to flush events before the worker freezes.

Cloudflare Workers run on the workerd runtime, which supports modern fetch-based libraries like @v-tilt/node. The catch: workers freeze the moment your handler returns, so you must call ctx.waitUntil(client.shutdown()) to make sure batched events actually flush.

#1. Install

npm install @v-tilt/node
bash
# wrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2025-01-01"
compatibility_flags = ["nodejs_compat"]

[vars]
VTILT_HOST = "https://www.vtilt.com"

# secrets — set via `wrangler secret put VTILT_TRACKER_TOKEN`
toml

#2. Module worker

// src/index.ts
import { VTiltNode } from '@v-tilt/node'

interface Env {
  VTILT_TRACKER_TOKEN: string
  VTILT_HOST: string
}

export default {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext,
  ): Promise<Response> {
    const vtilt = new VTiltNode(env.VTILT_TRACKER_TOKEN, {
      host: env.VTILT_HOST,
    })

    const url = new URL(request.url)
    if (url.pathname === '/track' && request.method === 'POST') {
      const { userId, event, properties } = await request.json<{
        userId: string
        event: string
        properties: Record<string, unknown>
      }>()

      vtilt.capture({ distinctId: userId, event, properties })
    }

    // Crucial: flush before the worker is frozen.
    ctx.waitUntil(vtilt.shutdown())

    return new Response(JSON.stringify({ ok: true }), {
      headers: { 'content-type': 'application/json' },
    })
  },
}
typescript

Important

Important: Constructing a fresh VTiltNode per request is fine — it's a small object. Reusing a module-scope singleton is also valid because workers can serve multiple requests on the same isolate, but you still need ctx.waitUntil(shutdown()) on every request to flush its buffered events.

#3. Identify users

If your worker sits in front of an authenticated app, identify on every request so server-side captures merge with the browser session.

const userId = await getUserIdFromToken(request.headers.get('authorization'))
if (userId) {
  vtilt.identify({
    distinctId: userId,
    anonymousId: getAnonCookie(request),
    properties: {
      /* ... */
    },
  })
}
typescript

#4. Scheduled events

Workers can also emit events from cron triggers. Same pattern — flush via ctx.waitUntil.

export default {
  async scheduled(
    _controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext,
  ) {
    const vtilt = new VTiltNode(env.VTILT_TRACKER_TOKEN, {
      host: env.VTILT_HOST,
    })

    vtilt.capture({
      distinctId: 'system',
      event: 'cron_ran',
      properties: { job: 'daily-summary' },
    })

    ctx.waitUntil(vtilt.shutdown())
  },
}
typescript

#5. As a reverse proxy

A common pattern is to proxy vTilt traffic through your own domain so ad blockers can't strip analytics. See Reverse proxy for a worker-based proxy example that forwards /_vt/* to your vTilt origin.

#Next steps

  • Reverse proxy — block-resistant ingestion via your own domain.
  • Node SDK / Context & shutdown — request-scoped identity.
  • Hono integration — same SDK with framework routing.
PreviousHonoIntegration guidesNextDjangoIntegration guides

On this page

  • 1. Install
  • 2. Module worker
  • 3. Identify users
  • 4. Scheduled events
  • 5. As a reverse proxy
  • Next steps