Skip to content

Commit 3318dcf

Browse files
authored
fix(analytics): wrap GA4 fetch in waitUntil to prevent dropped events (#59)
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.
1 parent 8db6c0a commit 3318dcf

3 files changed

Lines changed: 57 additions & 21 deletions

File tree

lib/analytics.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
* GA4_API_SECRET – created in GA4 Admin › Data Streams › Measurement Protocol API secrets
1111
*/
1212

13+
import { waitUntil } from "@vercel/functions";
14+
1315
const GA4_ENDPOINT = "https://www.google-analytics.com/mp/collect";
1416

1517
type EventParams = Record<string, string | number | boolean | undefined>;
@@ -41,14 +43,16 @@ export function trackEvent(
4143
if (v !== undefined) cleanParams[k] = v;
4244
}
4345

44-
fetch(url, {
45-
method: "POST",
46-
headers: { "Content-Type": "application/json" },
47-
body: JSON.stringify({
48-
client_id: clientId,
49-
events: [{ name, params: cleanParams }],
46+
waitUntil(
47+
fetch(url, {
48+
method: "POST",
49+
headers: { "Content-Type": "application/json" },
50+
body: JSON.stringify({
51+
client_id: clientId,
52+
events: [{ name, params: cleanParams }],
53+
}),
54+
}).catch((err) => {
55+
console.error(`[ANALYTICS] Failed to send event "${name}":`, err);
5056
}),
51-
}).catch((err) => {
52-
console.error(`[ANALYTICS] Failed to send event "${name}":`, err);
53-
});
57+
);
5458
}

package-lock.json

Lines changed: 43 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@types/react": "18.2.14",
2222
"@types/react-dom": "18.2.6",
2323
"@vercel/edge-config": "^0.2.1",
24+
"@vercel/functions": "^3.6.0",
2425
"autoprefixer": "10.4.14",
2526
"class-variance-authority": "^0.7.0",
2627
"eslint": "8.44.0",

0 commit comments

Comments
 (0)