Skip to content

fix(analytics): wrap GA4 fetch in waitUntil to prevent dropped events#59

Merged
Woody4618 merged 1 commit into
solana-developers:mainfrom
hoodieshq:fix/analytics-waituntil
May 25, 2026
Merged

fix(analytics): wrap GA4 fetch in waitUntil to prevent dropped events#59
Woody4618 merged 1 commit into
solana-developers:mainfrom
hoodieshq:fix/analytics-waituntil

Conversation

@askov
Copy link
Copy Markdown
Contributor

@askov askov commented May 25, 2026

Summary

The fire-and-forget fetch() in trackEvent() (lib/analytics.ts) returns a dangling Promise that the Vercel serverless runtime cancels the moment the response is returned. With GA4_MEASUREMENT_ID / GA4_API_SECRET correctly set in production, events were still never reaching google-analytics.com because the POST was cut off mid-flight — silently, since GA4's /mp/collect returns 204 regardless of whether anything was actually sent.

This was introduced together with the analytics module in #57.

Fix

Wrap the fetch in waitUntil() from @vercel/functions so the runtime keeps the function instance alive until the GA4 request settles — without blocking the airdrop response.

import { waitUntil } from "@vercel/functions";

waitUntil(
  fetch(url, { method: "POST", ... }).catch((err) => {
    console.error(`[ANALYTICS] Failed to send event "${name}":`, err);
  }),
);

Why waitUntil and not await?

await is also a valid fix — the cost is ~50–100 ms added to a request that's already 1–3 seconds (Turnstile + Solana RPC + backend logging). The choice for waitUntil here is to preserve the doc-stated contract that trackEvent "never blocks the airdrop response," and to isolate the airdrop path from any GA4 outage / TLS hang.

waitUntil is runtime-agnostic and reads a context object Vercel injects on globalThis. It does not change the deploy type, the runtime (Node.js Fluid Compute), regions, or pricing. Outside Vercel (self-hosted) it silently no-ops, matching the prior fire-and-forget semantics.

Test plan

  • npx vitest run — all 111 tests pass locally (no test changes required; existing tracking tests mock @/lib/analytics)
  • Deploy to a preview / staging environment with GA4_MEASUREMENT_ID + GA4_API_SECRET set
  • Trigger an airdrop and confirm airdrop_success appears in GA4 Realtime within ~30 sec
  • Trigger a failure path (invalid wallet) and confirm airdrop_failed with reason param appears
  • Optional: temporarily swap the endpoint to /debug/mp/collect once to validate payload shape (GA4 silently drops invalid events on the prod endpoint)

The fire-and-forget fetch() in trackEvent() returns a dangling Promise
that the Vercel serverless runtime cancels the moment the response is
returned. With GA4_MEASUREMENT_ID and GA4_API_SECRET correctly set in
production, events were still never reaching google-analytics.com
because the POST was cut off mid-flight.

Wrap the fetch in waitUntil() from @vercel/functions so the runtime
keeps the function instance alive until the GA4 request settles —
without blocking the airdrop response.

waitUntil is runtime-agnostic and reads a context object Vercel
injects on globalThis; it does not change the deploy type, the runtime
(Node.js Fluid Compute), regions, or pricing. Outside Vercel it
silently no-ops, matching the prior fire-and-forget semantics.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 25, 2026

@askov is attempting to deploy a commit to the Solana Foundation Team on Vercel.

A member of the Team first needs to authorize it.

@Woody4618 Woody4618 merged commit 3318dcf into solana-developers:main May 25, 2026
1 of 2 checks passed
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