Skip to content

Wire up Google Analytics 4 and tool_engaged conversion event#114

Merged
PavelMakarchuk merged 1 commit into
mainfrom
add-google-analytics
Apr 22, 2026
Merged

Wire up Google Analytics 4 and tool_engaged conversion event#114
PavelMakarchuk merged 1 commit into
mainfrom
add-google-analytics

Conversation

@PavelMakarchuk
Copy link
Copy Markdown
Collaborator

The bug

This app has been serving /us/marriage on policyengine.org since the Vercel rewrite was added (#105) but never had any analytics setup. Page views don't fire, tool_engaged doesn't fire, and Google Ads has been reporting zero conversions from every keyword that lands here since the rewrite went live.

Evidence

Headless test showed window.gtag undefined and zero tracking beacons on /us/marriage while every other calculator page (e.g. /us/aca-reforms-calculator) had working tracking. Reported CvR on the top 3 keywords landing here (marriage tax calculator, filing jointly vs separately, married vs single tax calculator) collapsed from 65–72% to 0% around the time the rewrite was deployed — consistent with silent tracking loss rather than any drop in actual engagement.

The fix

Match what policyengine-app-v2/website/src/app/layout.tsx and AppClient.tsx already do:

  • app/layout.jsx — add the GA4 bootstrap (gtag.js + config) in <head> using the same G-2YHG89FY0N property so conversions aggregate with the rest of policyengine.org. Uses next/script with afterInteractive so it doesn't block hydration.

  • app/MarriageApp.jsx — fire tool_engaged after 15 seconds on page, matching the threshold and event shape used by the main app's AppClient.tsx. Passes tool_name="marriage" and tool_title="Marriage Tax Calculator".

Test plan

After deploy:

  • Open https://policyengine.org/us/marriage in a fresh tab. Chrome DevTools → Network. Confirm /gtag/js loads and /g/collect fires with en=page_view.
  • Wait 15 seconds on the page. Confirm a second /g/collect fires with en=tool_engaged, ep.tool_name=marriage.
  • Check Google Analytics Realtime → ensure the marriage page appears alongside other tools.
  • Within 24–48 hours, check Google Ads → Campaigns → PolicyEngine: US → the marriage-family keywords should start showing conversions again.

🤖 Generated with Claude Code

This app has been serving /us/marriage on policyengine.org since the
Vercel rewrite was added (#105) but never had any
analytics setup. Page views and conversion events haven't been flowing
to GA4, and by extension Google Ads has been reporting zero conversions
from the top-converting search keywords (marriage tax calculator,
filing jointly vs separately, etc.) since the rewrite went live —
confirmed empirically by a 10× drop in reported CvR while the keywords
themselves are still performing.

Changes:

- app/layout.jsx — add the GA4 bootstrap (gtag.js + config) in <head>
  using the same G-2YHG89FY0N measurement ID as
  policyengine-app-v2/website/src/app/layout.tsx. Using next/script
  with afterInteractive so it doesn't block hydration.

- app/MarriageApp.jsx — fire the tool_engaged event after 15 seconds
  on the page, matching the signature and timing used by
  policyengine-app-v2/website/src/app/[countryId]/[slug]/AppClient.tsx
  so these conversions aggregate consistently with the other
  calculator tools. Passes tool_name="marriage" and
  tool_title="Marriage Tax Calculator".

Verification after deploy:
- Load /us/marriage in a fresh tab and confirm window.gtag is defined
  and a /g/collect beacon fires (Chrome DevTools Network tab).
- Wait 15 seconds and confirm a second /g/collect beacon fires with
  en=tool_engaged.
- Within ~24-48 hours, Google Ads should start showing conversions
  against marriage-family keywords again.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marriage Ready Ready Preview, Comment Apr 22, 2026 9:17pm

Request Review

@PavelMakarchuk PavelMakarchuk merged commit fd9f715 into main Apr 22, 2026
2 checks passed
@PavelMakarchuk PavelMakarchuk deleted the add-google-analytics branch April 22, 2026 21:21
PavelMakarchuk added a commit to PolicyEngine/policyengine-model that referenced this pull request Apr 22, 2026
This multizone app serves /us/model and /uk/model on policyengine.org
via the Vercel rewrite in policyengine-app-v2/website/next.config.ts,
but was split out without the GA4 bootstrap that the main website app
has — so all page_view events have been silently dropping ever since
the multizone rewrite went live.

This is the same failure mode we fixed for /us/marriage
(PolicyEngine/marriage#114). Applying the same pattern here.

Changes:

- app/layout.tsx — add the GA4 bootstrap (gtag.js + config) in <head>
  using the same G-2YHG89FY0N measurement ID as
  policyengine-app-v2/website/src/app/layout.tsx. Uses next/script
  with afterInteractive so it doesn't block hydration.

- app/client-layout.tsx — documents why this page deliberately does
  NOT fire the tool_engaged event that the calculator tools fire.
  tool_engaged is wired up as the Google Ads conversion trigger;
  firing it on reference content would inflate ad conversion counts.
  GA4's automatic user_engagement event still tracks engagement time
  and bounce rate without conflating with the conversion signal.

Verification after deploy:
- Load https://policyengine.org/us/model in a fresh tab and confirm
  window.gtag is defined and a /g/collect beacon fires with
  en=page_view (Chrome DevTools Network tab).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
PavelMakarchuk added a commit to PolicyEngine/policyengine-model that referenced this pull request Apr 22, 2026
This multizone app serves /us/model and /uk/model on policyengine.org
via the Vercel rewrite in policyengine-app-v2/website/next.config.ts,
but was split out without the GA4 bootstrap that the main website app
has — so all page_view events have been silently dropping ever since
the multizone rewrite went live.

This is the same failure mode we fixed for /us/marriage
(PolicyEngine/marriage#114). Applying the same pattern here.

Changes:

- app/layout.tsx — add the GA4 bootstrap (gtag.js + config) in <head>
  using the same G-2YHG89FY0N measurement ID as
  policyengine-app-v2/website/src/app/layout.tsx. Uses next/script
  with afterInteractive so it doesn't block hydration.

- app/client-layout.tsx — documents why this page deliberately does
  NOT fire the tool_engaged event that the calculator tools fire.
  tool_engaged is wired up as the Google Ads conversion trigger;
  firing it on reference content would inflate ad conversion counts.
  GA4's automatic user_engagement event still tracks engagement time
  and bounce rate without conflating with the conversion signal.

Verification after deploy:
- Load https://policyengine.org/us/model in a fresh tab and confirm
  window.gtag is defined and a /g/collect beacon fires with
  en=page_view (Chrome DevTools Network tab).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
PavelMakarchuk added a commit to PolicyEngine/policyengine-app-v2 that referenced this pull request Apr 22, 2026
Guards against a specific cross-repo failure mode we hit in April 2026:
when /us/marriage was rewired from the main app to a separate Vercel
zone (PolicyEngine/marriage), that zone had no GA4 bootstrap in its
layout — so every click silently lost both page_view and the
tool_engaged conversion event. Google Ads reported zero conversions
from the top-converting keywords for ~5 days before anyone noticed.

Neither repo's existing CI could catch it on its own: this PR
(routing) and the destination zone (missing layout scripts) lived in
different repos. The bug was only detectable by testing the composed
system end-to-end.

This check runs whenever website/next.config.ts changes. It parses
every rewrite destination, fetches the corresponding user-facing
policyengine.org URL, and verifies the GA4 measurement ID appears in
the served HTML. Fails the PR if any multizone is missing tracking.

Skips API-proxy rewrites (Modal backends, PostHog), static-asset
proxies (.svg/.png/etc.), and destinations we know don't need tracking
(legacy blog, internal slides). Handles :countryId parameter
substitution so dynamic routes get tested against a concrete URL
(defaults to /us).

Example output when healthy:

  Found 30 multizone destination(s) to audit.
  ✓ https://policyengine.org/us/marriage
      gtag present
  ...
  22/22 destinations have tracking.
  ✅ All multizone destinations have tracking.

Example output when broken (the April 2026 incident):

  ✗ https://policyengine.org/us/marriage
      GA4 measurement ID missing
  ❌ 1 destination(s) missing Google Analytics bootstrap:
    - https://policyengine.org/us/marriage (GA4 measurement ID missing)

Fix pattern: add the same gtag setup website/src/app/layout.tsx uses
to the destination repo's root layout. See PolicyEngine/marriage#114
and PolicyEngine/policyengine-model#21 for example PRs.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant