sendGAEvent Is Silently Dropping Your GA4 Events
Executive Summary (TL;DR)
When I audited the analytics on this site, GA4 had page views, scrolls and form starts. It had none of the events the code was sending. Not one click_contact_button. Not one generate_lead.
There was no console error and no warning in GA4. The contact form worked and the emails arrived. The conversion report said zero.
The tracking code looked fine:
sendGAEvent({
event: "generate_lead",
event_category: "Lead",
value: 1,
});
That is the syntax the Next.js documentation showed for sendGAEvent when @next/third-parties first shipped. It's the example quoted in vercel/next.js issue #61703, opened by someone whose custom events never arrived either. If your codebase has this pattern, it probably came from the docs.
It doesn't work, and nothing tells you.
Why the Object Form Is Ignored
@next/third-parties sets GA4 up with the standard gtag snippet. Then sendGAEvent does one thing: it pushes its arguments onto the dataLayer, the same way gtag() does. This is from the installed source, version 16.2.4:
function sendGAEvent(..._args) {
// ...guards for GA not being initialised...
window[currDataLayerName].push(arguments);
}
So sendGAEvent(a, b, c) behaves exactly like gtag(a, b, c). And gtag reads its first argument as a command: "config", "event", "set". An object in that position isn't a command, so gtag skips it.
The object form isn't random, either. It's how Google Tag Manager works: you push an object with an event key onto the dataLayer, and a GTM trigger listening for that name fires. With a GTM container on the page, that pattern is correct. With @next/third-parties there is no container listening. The object lands in the dataLayer and nothing ever reads it.
Older versions of the package had a second problem on top of this one: they passed a real array instead of the arguments object, which GA4 also ignored. That's discussed in the same issue. If you're on an old version, update before you debug anything else.
The Fix
The first argument is the string "event", the second is the event name, the third is the parameters:
- sendGAEvent({
- event: "generate_lead",
- event_category: "Lead",
- value: 1,
- });
+ sendGAEvent("event", "generate_lead", {
+ event_category: "Lead",
+ value: 1,
+ });
This is the form the current Next.js docs use. Search the codebase for sendGAEvent( and fix every call. On this site there were five: four contact buttons and the form.
Check It in Five Minutes
Don't take the fix on trust, including mine. Watch the event arrive.
- In GA4, open Reports → Realtime.
- Open the site in a private window, or on your phone. If you use the Google Analytics opt-out extension or an ad blocker, your normal browser sends nothing at all, and you'll conclude the fix failed.
- Trigger the event: click the button, or submit the form.
- Within a minute, the event name should appear in the Event count by Event name card.
If you want the event-by-event timeline instead, use Admin → DebugView. It only shows traffic with debug mode on, so either connect the site through Google Tag Assistant or pass debugMode to the GoogleAnalytics component while you test.
If the event doesn't show up, the problem is in the code. If it does show up and your conversion report still says zero, keep reading.
The Second Trap: Key Events That Never Fire
After the fix, events were arriving. The conversion report still said zero.
Under Admin → Events → Key events, two events were starred: close_convert_lead and qualify_lead. Both showed "No stream data detected."
Those are names from GA4's recommended lead events, the later stages of a lead: qualified, then closed. The site had never sent either one. The site sent generate_lead, and that event wasn't starred. So every real lead was counted as an ordinary event, and the conversion report stayed at zero.
The fix takes thirty seconds:
- Go to Admin → Events → Recent events.
- Star the event your site actually sends.
- Unstar every Key event your site doesn't send.
One catch: the Recent events tab only lists events seen in the last 28 days. On a low-traffic site, your lead event won't appear there until it has fired at least once. Trigger it yourself from a private window first.
Fire the Lead Event on Success, Not on Submit
Where the event fires matters as much as how. On this site, generate_lead fires only after the server confirms the notification email went out. It's skipped when the spam honeypot catches a bot, even though the bot still sees a success message.
One detail nearly broke that. Resend's emails.send() doesn't throw on an API error. It returns { data, error }. A try/catch on its own reports success for an email that was rejected, so your lead event fires for a lead you never received. Check error explicitly.
The Checklist
- Every
sendGAEventcall starts with"event". @next/third-partiesis on a current version.- The event shows up in Realtime from a private window.
- The event your site sends is the one starred as a Key event, and nothing else is.
- The lead event fires on confirmed success, not on click.
If your conversion report says zero and your inbox says otherwise, one of these five is the reason.