Dev Log

GA4: gtag, 4 auto-tracked events, floating Feedback button, privacy disclosure

· Claude Opus 4.7 (1M context) · Bot
analyticsga4productionprivacy

What was done

1. gtag.js installed in BaseLayout, production-only

  • src/layouts/BaseLayout.astro — gtag.js loaded via document.createElement("script") only when location.hostname === "baileyengineers.com" (or www.). Will NOT fire on prototype.baileyengage.com, localhost, or 127.0.0.1. This means dev work doesn’t pollute the production property and Kelli’s prototype reviews don’t either.
  • Measurement ID: G-8V80EQMMSB (the “Bailey Website - Production” GA4 property).
  • Uses is:inline so Astro doesn’t try to bundle/process the inline if-guard script.

2. Three event types auto-wired (single delegated listener)

  • contact_cta_click — fires on click of any element with a data-analytics-event attribute. The attribute value becomes the cta_label event parameter. Existing data-analytics-event annotations across the codebase (city page hero CTAs, role cards, project cards, etc.) automatically start firing — zero per-page edits needed.
  • outbound_click — fires on click of any anchor whose href resolves to a different host. Captures outbound_url and outbound_host parameters. Useful for understanding which content pushes users to LinkedIn, county sites, project portals, etc.
  • contact_submit — fires on form submission inside /contact/. Captures topic parameter from the URL ?topic= query (or the form’s data-topic attribute as fallback). Topic values: project, parcel-analysis, careers, iq-meet, feedback, unspecified.
  • All three guarded by if (typeof window.gtag !== "function") return; — script is a no-op on prototype/localhost where gtag wasn’t loaded.

3. scroll_75 and other engagement events

Not coded — handled by GA4’s built-in Enhanced Measurement feature. Confirm it’s ON in GA4 admin: Admin → Data Streams → click the stream → “Enhanced measurement” toggle. GA4 will auto-track scroll depth, outbound clicks (we duplicate this for consistency), site search, video engagement, file downloads.

4. Floating “Feedback” FAB

  • Bottom-right pill button, brand-black with precision-blue hover, glyph
  • Hidden on viewports under 600px (avoids covering content + accidental taps on phones)
  • Navigates to /contact/?topic=feedback (no JS — preserves zero-client-JS posture)
  • Annotated with data-analytics-event="feedback_open" so the click is captured as a contact_cta_click event before the user navigates

5. Privacy page rewritten

The previous privacy page asserted “no cookies, no visitor analytics, no marketing pixels” — true at the time, false now. Updated to:

  • New “What we collect from visitors” section explicitly disclosing GA4: aggregate, anonymized, IP-anonymized by default, city-level geographic only
  • Removed the “no Google Analytics” line from “What we don’t collect”
  • Added explicit “no marketing pixels / no cross-site tracking / no Google Signals” to make clear what’s still NOT happening
  • Added opt-out instructions: GA Opt-out Browser Add-on, Do Not Track, uBlock Origin, etc.
  • Added Google Analytics 4 to the “Third parties that process website data” list
  • Added Cloudflare to the same list (was missing — Cloudflare fronts the production site)
  • Added “Site feedback submissions go to careers@” line under the routing list

6. CLAUDE.md + memory updated

  • New section in CLAUDE.md: “What BaseLayout.astro automatically handles (do NOT duplicate per-page)” — documents that GA4, OG images, JSON-LD schemas, title-suffix logic, BreadcrumbList, and the Feedback FAB all flow through BaseLayout/Breadcrumbs automatically. Future contributors won’t accidentally duplicate.
  • Memory: reference_production_hosting.md — captures the deployment topology (Cloudflare → cPanel Droplet → /home/adminl/public_html/) so the Spaces confusion doesn’t repeat.

Why

Per Ryan: “GA4 only” decided, measurement ID G-8V80EQMMSB provided, careers@ confirmed as feedback destination, production-only firing confirmed. This is the install pass; the next session will be cutover-day work (deploy + Cloudflare cache purge + GA Realtime verification).

Files affected

Modified

  • src/layouts/BaseLayout.astro — gtag block in <head>, event-tracking script + Feedback FAB in <body>, FAB CSS
  • src/pages/privacy.astro — Summary, “What we collect”, “What we don’t collect”, “Third parties” sections rewritten for GA4 honesty
  • CLAUDE.md — new “What BaseLayout automatically handles” section
  • src/content/devlog/MEMORY.md index updated (memory cluster)

Created

  • ~/.claude/projects/.../memory/reference_production_hosting.md — deployment topology memory
  • src/content/devlog/2026-04-26-ga4-events-feedback-button-privacy-update.md (this entry)

Cutover-day playbook (when Ryan FTPs to prod)

  1. Build: npm run build:verify (already done — 20/20 PASS)
  2. SFTP dist/* to /home/adminl/public_html/ via FileZilla (overwrite existing)
  3. Purge Cloudflare cache — required, or visitors see old cached HTML for hours
  4. Verify GA4 events arriving: GA4 → Reports → Realtime → click around the live site, watch events appear
  5. Mark contact_submit as a key event: GA4 → Admin → Events → toggle “Mark as key event”
  6. Confirm Enhanced Measurement is on for the data stream (auto-tracks scroll, file downloads, outbound clicks, site search, video — useful redundancy)

Verification

  • Build verify: 20/20 PASS
  • gtag.js block present in dist/index.html, hostname-guarded (only fires on baileyengineers.com)
  • Feedback FAB present on all sampled pages (homepage, pillar, city page) — 1 instance per page (BaseLayout-injected, not duplicated)
  • Privacy page contains “Google Analytics 4” disclosure (1 occurrence in summary, more in the third-party list)
  • No analytics events fire on prototype/localhost (manual test: npm run dev → console shows no GA requests)
Feedback