Skip to main content

Design Notes

A few behavioral details worth understanding before you deploy @gtmss/ga4-relay to production.

Same-request token propagation

withGa4Token's cookie is only visible to the browser's next request. The collect handler reads the token from the x-ga4-relay-token header (GA4_TOKEN_HEADER) that your middleware.ts forwards on the current request, falling back to the cookie for anything that only ever sets it there. If you write your own middleware without the header-forwarding step described in Installation, every clean browser's first request will 401.

Service worker scope

The reference wiring serves the SW from /ga4-relay/ga4-sw.js (a nested path), giving it a natural default scope of /ga4-relay/ — narrow enough that it can never take over a host site's existing service worker at a broader scope. Widening this via Service-Worker-Allowed to / is possible but is a deliberate opt-in you should only make after confirming there's no conflicting host SW; two service workers cannot both control the same scope; the second registration replaces the first there.

Dedupe fail-open vs. fail-closed

A dedupe-store outage fails open (forwards the event) for a fresh, never-before-sent live event — silently dropping brand-new traffic while returning 200 would be undetectable data loss. It fails closed (drops the event) for a replayed queued event, where the event has already been attempted once and losing it again is the safer side, matching Principle 5 (retry and dedupe ship together, never separately).

Delivery is best-effort in v1

after()/waitUntil() work is cancelled if the function times out and isn't durable across an instance dying mid-request. At-least-once delivery would require a durable queue (QStash/Vercel Queues) — explicitly out of scope for v1.

Rate limiting requires Upstash in production

The in-memory Store is dev-only; createGa4Relay throws at construction if it's used with rate limiting enabled while NODE_ENV=production.