Skip to content

Instantly share code, notes, and snippets.

@chillu
Last active April 4, 2023 18:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save chillu/0f54097117094d9ad7247727b5e4a9cd to your computer and use it in GitHub Desktop.
Save chillu/0f54097117094d9ad7247727b5e4a9cd to your computer and use it in GitHub Desktop.
UTM Runn
<html>
<head>
<script type="text/javascript" src="/script.js"></script>
</head>
<body>
<p>Internal nav</p>
<ul>
<li><a href="/index.html">Home without UTM</a></li>
<li><a href="/blog/my-blog-post.html">Blog post without UTM</a></li>
<li><a href="/other-page.html">Other page without UTM</a></li>
<li><a href="/index.html?utm_source=existing&amp;utm_content=existing&amp;utm_medium=existing&amp;utm_campaign=existing">Home with UTM</a></li>
<li><a href="/blog/my-blog-post.html?utm_source=existing&amp;utm_content=existing&amp;utm_medium=existing&amp;utm_campaign=existing">Blog post with UTM</a></li>
<li><a href="/other-page.html?utm_source=existing&amp;utm_content=existing&amp;utm_medium=existing&amp;utm_campaign=existing">Other page with UTM</a></li>
</ul>
<p>Test links</p>
<ul>
<li><a href="https://app.runn.io/users/sign_up?utm_source=my-link-source&amp;utm_campaign=my-link-campaign">App signup link with UTM</a></li>
<li><a href="https://app.runn.io/users/sign_up">App signup link without UTM</a></li>
<li><a href="https://app.runn.io/users/login">App login link</a></li>
<li><a href="/internal-link-utm?utm_source=my-link-source&amp;utm_campaign=my-link-campaign">Internal link with UTM</a></li>
<li><a href="/internal-link">Internal link without UTM</a></li>
<li><a href="https://my-link-source.com">External link</a></li>
<li><a href="relative-link-unrelated-param/?unrelated_param=true">Link with unrelated param</a></li>
<li><a href="relative-link-unrelated-param/?unrelated_param=true" class="ga-track" data-ga-category="bla">Custom GA event link</a></li>
<li><a href="relative-link-unrelated-param/?unrelated_param=true" data-plausible-goal="Custom goal" data-ga-category="bla">Custom Plausible goal link</a></li>
</ul>
</body>
</html>
<html>
<head>
<script type="text/javascript" src="/script.js"></script>
</head>
<body>
<p>Internal nav</p>
<ul>
<li><a href="/index.html">Home without UTM</a></li>
<li><a href="/blog/my-blog-post.html">Blog post without UTM</a></li>
<li><a href="/other-page.html">Other page without UTM</a></li>
<li><a href="/index.html?utm_source=existing&amp;utm_content=existing&amp;utm_medium=existing&amp;utm_campaign=existing">Home with UTM</a></li>
<li><a href="/blog/my-blog-post.html?utm_source=existing&amp;utm_content=existing&amp;utm_medium=existing&amp;utm_campaign=existing">Blog post with UTM</a></li>
<li><a href="/other-page.html?utm_source=existing&amp;utm_content=existing&amp;utm_medium=existing&amp;utm_campaign=existing">Other page with UTM</a></li>
</ul>
<p>Test links</p>
<ul>
<li><a href="https://app.runn.io/users/sign_up?utm_source=my-link-source&amp;utm_campaign=my-link-campaign">App signup link with UTM</a></li>
<li><a href="https://app.runn.io/users/sign_up">App signup link without UTM</a></li>
<li><a href="https://app.runn.io/users/login">App login link</a></li>
<li><a href="/internal-link-utm?utm_source=my-link-source&amp;utm_campaign=my-link-campaign">Internal link with UTM</a></li>
<li><a href="/internal-link">Internal link without UTM</a></li>
<li><a href="https://my-link-source.com">External link</a></li>
<li><a href="relative-link-unrelated-param/?unrelated_param=true">Link with unrelated param</a></li>
<li><a href="relative-link-unrelated-param/?unrelated_param=true" class="ga-track" data-ga-category="bla">Custom GA event link</a></li>
<li><a href="relative-link-unrelated-param/?unrelated_param=true" data-plausible-goal="Custom goal" data-ga-category="bla">Custom Plausible goal link</a></li>
</ul>
</body>
</html>
<html>
<head>
<script type="text/javascript" src="/script.js"></script>
</head>
<body>
<p>Internal nav</p>
<ul>
<li><a href="/index.html">Home without UTM</a></li>
<li><a href="/blog/my-blog-post.html">Blog post without UTM</a></li>
<li><a href="/other-page.html">Other page without UTM</a></li>
<li><a href="/index.html?utm_source=existing&amp;utm_content=existing&amp;utm_medium=existing&amp;utm_campaign=existing">Home with UTM</a></li>
<li><a href="/blog/my-blog-post.html?utm_source=existing&amp;utm_content=existing&amp;utm_medium=existing&amp;utm_campaign=existing">Blog post with UTM</a></li>
<li><a href="/other-page.html?utm_source=existing&amp;utm_content=existing&amp;utm_medium=existing&amp;utm_campaign=existing">Other page with UTM</a></li>
</ul>
<p>Test links</p>
<ul>
<li><a href="https://app.runn.io/users/sign_up?utm_source=my-link-source&amp;utm_campaign=my-link-campaign">App signup link with UTM</a></li>
<li><a href="https://app.runn.io/users/sign_up">App signup link without UTM</a></li>
<li><a href="https://app.runn.io/users/login">App login link</a></li>
<li><a href="/internal-link-utm?utm_source=my-link-source&amp;utm_campaign=my-link-campaign">Internal link with UTM</a></li>
<li><a href="/internal-link">Internal link without UTM</a></li>
<li><a href="https://my-link-source.com">External link</a></li>
<li><a href="relative-link-unrelated-param/?unrelated_param=true">Link with unrelated param</a></li>
<li><a href="relative-link-unrelated-param/?unrelated_param=true" class="ga-track" data-ga-category="bla">Custom GA event link</a></li>
<li><a href="relative-link-unrelated-param/?unrelated_param=true" data-plausible-goal="Custom goal" data-ga-category="bla">Custom Plausible goal link</a></li>
</ul>
</body>
</html>
const LS_KEY = 'runnUtmParams';
const ADD_TRACKING_LINKS_REGEX = /app.runn.io\/users\/sign_up/;
const DEFAULT_SOURCE = 'runn.io';
function paramFilter(params, re) {
let filtered = {}
params.forEach((value, key) => {
if (key.match(re)) {
filtered[key] = value;
}
})
return filtered;
}
function addNewParams(href, params) {
const urlObj = new URL(href);
Object.keys(params).forEach(param => {
// Don't overwrite
if (urlObj.searchParams.get(param)) return;
urlObj.searchParams.set(param, params[param]);
})
return urlObj.toString();
}
function storeTracking(currentHref) {
const ls = window.localStorage;
if (!ls) {
return
}
// Only store the first time a visitor arrives.
// This ensures the "landing page" is tracked.
if (ls.getItem(LS_KEY)) {
return
}
const currentUrl = new URL(currentHref);
let utmParams = paramFilter(currentUrl.searchParams, /^utm/);
// Track landing page on first visit to provide more attribution context.
// Only do this when no UTM is present, to avoid mixing UTM data from different contextx.
if (currentUrl.pathname.match(/^\/blog/) && !Object.keys(utmParams).length) {
const source = document.referrer ? (new URL(document.referrer)).hostname : null;
utmParams = {
utm_medium: 'blog',
utm_campaign: 'organic',
utm_source: source ? source : DEFAULT_SOURCE,
utm_content: currentUrl.pathname,
}
}
ls.setItem(LS_KEY, JSON.stringify(utmParams));
}
// Ensure UTM tracking survives internal navigation without requiring explicit UTM
// on certain outgoing links, and allows more granular attribution.
// This is a workaround for not having continuous tracking tooling on sessions between
// the website and app (for privacy and regulatory reasons).
function addTrackingToAppLinks(currentHref) {
const currentUrl = new URL(currentHref);
const utmParams = window.localStorage ? JSON.parse(window.localStorage.getItem(LS_KEY)) : null;
if (!utmParams) {
return;
}
Array.from(document.getElementsByTagName('a')).forEach(el => {
if(!el.href || !el.href.match(ADD_TRACKING_LINKS_REGEX)) {
return;
}
// Add new params *without* overwriting existing ones on the link
el.href = addNewParams(el.href, utmParams);
});
}
// Custom event tracking defined on link elements
function addEventTrackingToLinks() {
Array.from(document.getElementsByClassName('ga-track')).forEach(el => {
el.addEventListener('click', () => {
// Plausible tracking
if (typeof plausible !== 'undefined') {
plausible(el.dataset.plausibleGoal ? el.dataset.plausibleGoal : 'Signup');
}
// Google Analytics tracking
if (typeof window.ga !== 'undefined') {
const gaEvent = {
hitType: 'event',
eventAction: 'click',
eventCategory: 'unknown',
transport: 'beacon',
}
if (el.dataset.gaCategory) { gaEvent.eventCategory = el.dataset.gaCategory }
if (el.dataset.gaAction) { gaEvent.eventAction = el.dataset.gaAction }
if (el.dataset.gaLabel) { gaEvent.eventLabel = el.dataset.gaLabel }
window.ga('send', 'event', gaEvent);
}
});
})
}
window.addEventListener('DOMContentLoaded', (event) => {
storeTracking(document.location.href)
addTrackingToAppLinks(document.location.href)
addEventTrackingToLinks()
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment