Skip to content

First-party proxy

@plaintrace/server runs next to your app. It serves the tracker from your own domain (past ad blockers), records crawler reads that no browser script can see, counts human page loads so reports show measured loss, and sends events from your backend. It uses web standards only (Request, Response, fetch, crypto.subtle): Node 24+, Bun, Deno and Cloudflare Workers.

Terminal window
npm install @plaintrace/server
import { createClient } from '@plaintrace/server'
const analytics = createClient({
key: process.env.ANALYTICS_KEY!, // an ingest key: never ship it to a browser
endpoint: 'https://collect.plaintrace.dev',
ipHeader: 'x-real-ip',
})
// In your middleware, before the app:
const res = await analytics.handle(request, { waitUntil: ctx.waitUntil })
if (res) return res

Then point the browser at your proxy path with data-endpoint (for example /_i).

A visitor can send any header themselves, so by default only Cloudflare Workers’ CF-Connecting-IP is trusted. Elsewhere set ipHeader to the one header your own edge overwrites:

Platform ipHeader
Vercel x-vercel-forwarded-for
Fly.io fly-client-ip
nginx x-real-ip
Other proxies x-forwarded-for (read from its right end)

Or pass ip yourself.

Only the user agent, language, origin, referer, Sec-Fetch-*, client hints and Sec-GPC. Cookies, your own Authorization and Host never do. Every call is signed with HMAC over the method, path, body and time.