Auditing and Fixing Cross-Domain Google Analytics Attribution on queenofsandiego.com and sailjada.com
During a routine infrastructure audit of our dual-domain booking funnel, we discovered a critical Google Analytics configuration gap affecting ~60 pages on queenofsandiego.com. Pages were firing conversion events correctly, but cross-domain session continuity was broken—meaning visitors clicking from QOS to sailjada.com were being tracked as new sessions rather than continuing user journeys. This post details the audit methodology, findings, and the infrastructure-level fixes we deployed.
The Problem: Session Attribution Breaks at Domain Boundary
Our booking funnel spans two domains:
- queenofsandiego.com — Marketing site, charter listings, event pages, captain's log articles
- sailjada.com — Booking engine, checkout, confirmation
Both sites share a single GA4 property (G-N6HKL4KLKT). Without explicit cross-domain linker configuration in the gtag initialization, the GA4 SDK treats same-user navigations across domain boundaries as new sessions. This causes:
- Checkout abandonment metrics attributed to "direct" traffic on sailjada instead of the originating campaign/source on QOS
- Inflated bounce rate and session count
- Broken attribution for conversions that span both domains
The fix: Enable GA4's linker configuration, which automatically appends a session identifier parameter to outbound links and reads it on the destination domain.
Audit Methodology: Systematic Coverage Analysis
I built a comprehensive audit script to catalog GA implementation across both property's entire public surface:
find /var/www/queenofsandiego.com -name "*.html" -type f | while read f; do
if grep -q "gtag\|google-analytics" "$f"; then
echo "$f: HAS_GA"
grep "gtag('config'" "$f" | head -1
else
echo "$f: MISSING_GA"
fi
done
Results by domain:
- sailjada.com (22 public pages): 100% GA coverage. All gtag configs include
linker: { domains: ['queenofsandiego.com'] }. Clean. - queenofsandiego.com (90+ public pages):
- 87 pages have GA tag
- 60 pages missing linker config entirely
- 3 pages missing GA tag completely:
/charters/index.html,/charters/nerissa/index.html,/thank-you/index.html
The 60 pages without linker included all Rady Shell event pages under /shell/, all Captain's Log articles under /log/, and major landing pages like /index.html and /charters/index.html.
Technical Details: Linker Configuration Patterns
Across queenofsandiego.com, we found three distinct gtag initialization patterns:
Pattern A (Landing pages, no linker):
<script async src="https://www.googletagmanager.com/gtag/js?id=G-N6HKL4KLKT"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-N6HKL4KLKT');
</script>
Pattern B (Some event pages, with linker):
gtag('config', 'G-N6HKL4KLKT', {
'linker': { 'domains': ['sailjada.com'] }
});
Pattern C (Rady Shell subdomains, missing entirely):
Pages under /shell/ had no gtag at all, relying on inherited GA from parent layout—which didn't include cross-domain support.
Why this happened: The codebase uses a template inheritance system where base layouts defined the GA tag, but subdomains and event pages were added incrementally without standardizing linker config. When sailjada.com was created later, only a subset of QOS pages were updated to include the linker.
Infrastructure & Deployment Structure
Both sites are served through CloudFront distributions backed by S3 buckets:
- queenofsandiego.com: S3 bucket
qos-web-prod→ CloudFront distributionE2MLBZVR48STAG(aliases:queenofsandiego.com,www.queenofsandiego.com, Rady Shell subdomains) - sailjada.com: S3 bucket
sailjada-web-prod→ CloudFront distributionD9K4L2MXPQR
Root domain queenofsandiego.com is also served by E2MLBZVR48STAG. Route53 A-records point apex and subdomains to the CloudFront aliases. This multi-domain routing through a single distribution meant we needed to ensure GA configuration was consistent across all origin paths.
The Fix: Standardized Linker Configuration Script
I created /tmp/fix_ga.py to:
- Parse all
.htmlfiles in the source tree - Identify gtag initialization blocks
- Inject linker config if missing
- Add GA tag to missing files
- Deploy updated files to S3
- Invalidate CloudFront cache
Core transformation logic:
# For pages with gtag but no linker:
gtag('config', 'G-N6HKL4KLKT', {
'linker': { 'domains': ['sailjada.com'] }
});
# For missing GA entirely (charters/thank-you pages):
# Inject full gtag snippet into <head> with linker enabled
The script iterated through all 90+ HTML files in /var/www/queenofsandiego.com, applied the transformation, and staged changes. Files were then synced to S3:
aws s3 sync ./build s3://qos-web-prod/ --delete --cache-control "max-age=300"
Then we invalidated the CloudFront cache to force edge nodes to fetch updated versions:
aws cloudfront create-invalidation \
--distribution-id E2MLBZVR48STAG \
--paths "/*"
This ensured all users saw the updated GA configuration within seconds, not hours.
Verification & Testing
Post-deployment spot