From e65a3a02274ba7a5bff5201e63a1770bd4822e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filiph=20Siitam=20Sandstr=C3=B6m?= Date: Wed, 19 Jul 2023 01:07:58 +0200 Subject: [PATCH] feat: add support for ga4 cross-domain measurements This isn't nearly as straightforward as it would initially seem. Additionally, it's essentially required for some store setups to get any form of accurate attribution/data tracking at all. **Note: To get it working you'll need to include Google Analytics 4's gtag.js and make sure it gets loaded and executed before the body does. In other words, make sure it's a blocking script in the `` tag.** - (there isn't really a great way to listen for gtag being initialized and even if there was it wouldn't make sense to waste time on loading it async since the whole goal is to redirect the user anyways). *Based on code I wrote privately for https://www.sweetsideofsweden.com now available under MIT. I pretty much had to figure out this workaround through basically reverse-engineering. Hopefully this will save people from also having to go through that pain.* :^) --- config/settings_schema.json | 7 +++++ layout/theme.liquid | 56 ++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/config/settings_schema.json b/config/settings_schema.json index 0f5127c..02557c2 100644 --- a/config/settings_schema.json +++ b/config/settings_schema.json @@ -32,6 +32,13 @@ "default": false, "label": "Multipass login", "info": "If multipass is implemented the login will also redirect to your storefront [Learn more](https://shopify.dev/api/multipass)" + }, + { + "type": "checkbox", + "id": "ga4_crossdomain", + "default": false, + "label": "GA4 cross-domain measurements", + "info": "Required to collect accurate attribution data from customers getting redirected by this theme [Learn more](https://support.google.com/analytics/answer/10071811?hl=en). For this to work you will need to have gtag.js loaded in as a blocking script." } ] }, diff --git a/layout/theme.liquid b/layout/theme.liquid index ef692b5..8e6a6c0 100644 --- a/layout/theme.liquid +++ b/layout/theme.liquid @@ -208,7 +208,14 @@ } window.storefrontRedirectUrl = redirectUrl; - window.location.replace(redirectUrl); + + {%- comment -%} + When we're using the ga4 work-around, we'll have to patiently wait for gtag.js to complete + it's initialization.. so we will have to redirect later in the body. + {%- endcomment -%} + {%- unless settings.ga4_crossdomain -%} + window.location.replace(redirectUrl); + {%- endunless -%} } } @@ -229,6 +236,53 @@ if (!window.Shopify.designMode) { var redirectLink = document.getElementById("redirect-link"); redirectLink.href = window.storefrontRedirectUrl; + {%- if settings.ga4_crossdomain -%} + {%- comment -%} + Ugly but effect workaround for ga4 not properly (or kind of at all) programatically invoked + navigation events for cross-domain measurements. + + We're esentially exploiting the fact that analytics try to "decorate" html forms when they + have an action that points to a domain included in the list that you've configured under + Data Streams -> Configure tag settings -> Configure your domains; in the analytics property + settings. See https://support.google.com/analytics/answer/10071811. + + This code was originally written to fix cross-domain measurements the other way around, eg + between the headless frontend and the Shopify-hosted checkout for the e-commerce store + https://www.sweetsideofsweden.com. So you can repurpose this code to get proper cross-domain + measurements for you're checkouts too. :^) + {%- endcomment -%} + function getCrossDomainLinkerParameter() { + const form = document.createElement("form"); + form.action = "https://{{ settings.storefront_hostname }}"; + {%- comment -%} + Note, `opacity: 0` is deliberately used since the ga4 decorator wouldn't be or isn't able to + detect `display: none` + {%- endcomment -%} + form.style.opacity = "0"; + form.addEventListener("submit", (event) => { + event.preventDefault(); + }); + const btn = document.createElement("button"); + btn.type = "submit"; + form.append(btn); + document.body.append(form); + btn.click(); + + {%- comment -%} + This could fail for a magnitude of reasons such as ad-blocking, invalid configuration, etc. + But when it doesn't fail, which is most of the times we get full complete cross-domain measurements. + {%- endcomment -%} + const _gl = form.querySelector("input[name=\"_gl\"]"); + if (_gl) return _gl.value; + return null; + } + + const ga4 = getCrossDomainLinkerParameter(); + if (ga4) { + redirectLink.href += `${redirectLink.href.includes("?") && "&" || "?"}_gl=${ga4}`; + redirectLink.click(); + } + {%- endif -%} } {%- else -%}