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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants