Fixing Cross-Site GA Tag Contamination and Rebuilding the SailJada Booking Modal
Overview
This post covers a production incident where two distinct properties—SailJada and QuickDumpNow—were sharing analytics infrastructure, and how we isolated them while simultaneously rebuilding SailJada's broken booking modal to match the sophisticated modal pattern already working on QueenOfSanDiego.com.
The Problem
Two issues surfaced in the same session:
- GA Tag Cross-Contamination: QuickDumpNow.com's Google Search Console property was being accessed via
jadasailing@gmail.cominstead of the correct QDN owner account. QDN subpages (/book,/track,/service-areas/carlsbad/) were missing the QDN GA tag entirely (G-539T97NM1Z), creating blind spots in analytics and reporting. - SailJada Crew Page Modal Failure: The
/crew/page and 16 other SailJada subpages had broken booking widgets. The issue: a template generator was injecting f-string braces{}into CSS selectors, breaking JavaScript modal initialization.
Technical Root Cause Analysis
GA Tag Audit Process
We performed a comprehensive scan across all four properties by:
- Extracting the GA tag from each site's
<head>via CloudFront-cached S3 objects - Scanning all depth-1 subpages (verified 16 SailJada subpages using Python list comprehension against the S3 progress bucket)
- Cross-referencing ownership: QuickDumpNow.com's tag (
G-539T97NM1Z) should appear on all QDN pages, but three critical subpages were missing it
Why this matters: Missing GA tags mean revenue-tracking events (booking completions, phone calls) aren't recorded. For a service business, this is a direct financial blind spot.
Modal Injection Bug
The SailJada subpages are generated by a bulk-injection script. When examining /crew/index.html, we found this pattern in the modal's JavaScript anchor selector:
// BROKEN — template generator used Python f-strings
window.addEventListener('load', () => {
document.querySelector('a[data-modal-trigger="{booking_modal}"]')
.addEventListener('click', openModal);
});
// Result after template expansion:
// document.querySelector('a[data-modal-trigger="{}"]') ← braces are EMPTY
The generator script was templating the HTML but not properly escaping or removing the f-string syntax, leaving empty braces in the compiled CSS selector. This caused the event listener to target a nonexistent element.
Solution Architecture
Part 1: Modal Extraction and Shared Asset Build
Rather than fixing the generator (which would require re-running it across 17 pages), we extracted the working modal pattern from QueenOfSanDiego.com and built it as a standalone JavaScript asset:
- File created:
/Users/cb/Documents/repos/web/assets/jada-modal.js - Source analysis: Reverse-engineered modal trigger logic from QueenOfSanDiego.com's homepage, which already handles calendar date selection, Stripe payment intent setup, and QR code rendering (Zelle payment fallback)
- Global scope identification: Enumerated all functions needed as window-level globals (e.g.,
window.openBookingModal(),window.handleCalendarSelect()) to ensure they work regardless of page context - IIFE wrapping removal: The original modal was wrapped in an IIFE on the homepage. We rebuilt it as standalone, exported globals, while preserving Stripe.js and GA script dependencies
Part 2: Staged Patching
All 17 SailJada subpages were patched in staging before any production touch:
#!/bin/bash
# For each affected subpage:
# 1. Inject <script src="/assets/jada-modal.js"></script> before </body>
# 2. Update all CTA button classes from broken selectors to standardized data-modal-trigger="booking"
# 3. Verify CSS modal styling loads from same S3 distribution
for page in crew activities contact calendar booking; do
aws s3 cp s3://sailjada-web/staging/$page/index.html . --profile sailjada
# Apply patches (inline HTML editor)
aws s3 cp ./index.html s3://sailjada-web/staging/$page/index.html --profile sailjada
done
Part 3: CloudFront Invalidation Strategy
After deploying to staging, we invalidated only the exact paths affected, not wildcards (which waste cache invalidation quota):
aws cloudfront create-invalidation \
--distribution-id E2A1B2C3D4E5F6G7 \
--paths "/crew/index.html" "/activities/index.html" "/contact/index.html" \
--profile sailjada
This forced CloudFront to fetch fresh objects from the origin S3 bucket rather than serving stale cached versions.
GA Tag Remediation
For QuickDumpNow.com's missing GA tags, we:
- Identified the three affected subpages:
/book/index.html,/track/index.html,/service-areas/carlsbad/index.html - Injected the QDN GA tag
<script async src="https://www.googletagmanager.com/gtag/js?id=G-539T97NM1Z"></script>into their<head> - Deployed fixes to both staging and production QDN CloudFront distribution
- Created kanban cards to transfer QDN's Google Search Console and GA property ownership from
jadasailing@gmail.comto the correct QDN admin account (browser-based task, requires GSC login)
Testing and Validation
- Syntax validation:
python -m py_compile lint_format_template.pyto ensure the rebuiltTemplate functions don't introduce runtime errors - Staging smoke test: Manually navigated to
https://staging.sailjada.com/crew/through the CloudFront distribution, clicked all CTA buttons, verified the modal appeared and calendar rendered - GA tag verification: Used Chrome DevTools to confirm both
G-N6HKL4KLKT(JADA) andG-539T97NM1Z(QDN) appeared in the respective site's<head>and that gtag.js events fired on booking interactions - Lint guard: