Skip to content

feat(server): add trackExposure - #209

Merged
feugy merged 2 commits into
canaryfrom
damienfeugas/exp-3368-port-trackexposure-from-toolkit-to-analytics
Aug 26, 2026
Merged

feat(server): add trackExposure#209
feugy merged 2 commits into
canaryfrom
damienfeugas/exp-3368-port-trackexposure-from-toolkit-to-analytics

Conversation

@feugy

@feugy feugy commented Aug 26, 2026

Copy link
Copy Markdown
Member

🖖 What's in there?

Ports trackExposure() from the @vercel/toolkit prototype to @vercel/analytics/server, so vercel-marketing can report experiment exposures from a canary release.

import { trackExposure } from '@vercel/analytics/server';

await trackExposure({
  experimentId: 'checkout-redesign',
  variantId: 'treatment',
  unitKey: 'user',
  unitValue: user.id,
  userId: user.id,
  props: { plan: 'pro' },
});

It posts to /_vercel/insights/exposure, which api-analytics-ingestion already serves as an alias of /v1/event tagged t: 'exposure'. userId, groupId and props go top-level, matching the fields that service reads and its 256-char truncation.

Server-side only. Browser exposure tracking is a separate ticket.

Landing it meant track and trackExposure shared five near-identical blocks, so the request plumbing now lives in server/request.ts:

  • rejectBrowserRuntime() — the typeof window guard
  • resolveEndpoint('event' | 'exposure') — adds VERCEL_WEB_ANALYTICS_EXPOSURE_ENDPOINT, and treats VERCEL_WEB_ANALYTICS_ENDPOINT as a base URL for exposures while keeping it verbatim for events
  • reportMissingEndpoint() — the dev/prod logging when no endpoint resolves
  • dispatch() — request context, header forwarding, the shared envelope, the POST, and the catch-all

track dropped from 84 to 30 lines with no behaviour change: server/index.test.ts is untouched and still green. The server bundle went 8.03 → 7.41 KB.

🤺 How to test?

pnpm -F @vercel/analytics build
pnpm -r test          # 136 unit tests, 18 of them new for trackExposure

src/server/index.test.ts is deliberately unmodified — it is the guardrail proving the track refactor is behaviour-preserving.

🔬 Notes to reviewers

Two intentional behaviour changes:

  1. track's browser-guard message said @vercel/web-analytics/server, which is not an importable path. Sharing the guard forced one string, so both now say @vercel/analytics/server. Assertion updated in browser.test.ts.
  2. x-va-server: '2' is gone. It sat in a branch guarded by hasHeaders, after an unconditional throw on !hasHeaders — unreachable, so ingestion never received it. Restore the ternary in dispatch() if sending without session headers is ever meant to work.

experimentId over 255 chars is dropped by ingestion with a 202. The SDK does not truncate it, same as the prototype.

@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
analytics-astro Ready Ready Preview Aug 26, 2026 3:25pm
analytics-next15 Ready Ready Preview Aug 26, 2026 3:25pm
analytics-nextjs Ready Ready Preview Aug 26, 2026 3:25pm
analytics-nuxt Ready Ready Preview Aug 26, 2026 3:25pm
analytics-remix Ready Ready Preview Aug 26, 2026 3:25pm
analytics-sveltekit Ready Ready Preview Aug 26, 2026 3:25pm
analytics-sveltekit-old Ready Ready Preview Aug 26, 2026 3:25pm
analytics-vue Ready Ready Preview Aug 26, 2026 3:25pm

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants