Skip to content

Fix UTM tracking when canonical link present - #260

Merged
stordahl merged 2 commits into
benvinegar:mainfrom
jkseppan:canonical
Sep 9, 2026
Merged

stordahl merged 2 commits into
benvinegar:mainfrom
jkseppan:canonical

Conversation

@jkseppan

@jkseppan jkseppan commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Overview

Change trackPageview so that the UTM parameters are extracted from the actual browser URL instead of any <link rel="canonical"> URL on the page. This way the UTM parameters in the link clicked by the user are not silently lost.

Changes by Package

@counterscale/tracker

trackPageview now uses opts.url (if present) or window.location.url as the source of UTM parameters, not any <link rel="canonical"> URL on the page.

Additional Notes

fixes #259

UTM params must come from the visited URL, not the canonical one.

fixes benvinegar#259

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCVeuQZukeTcQn42dHemB8

@stordahl stordahl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the report and the fix — the diagnosis is correct and the new integration test is a good reproduction of #259. Requesting changes for the readability of the call site: the behavior is right, but the code reads ambiguously.

The change

track.ts:91-95 now has two adjacent fallback chains with different second legs:

const url = opts.url || location.pathname + location.search || "/";   // canonical-aware
...
const utmParams = getUtmParamsFromBrowserUrl(
    opts.url || window.location.search,                               // canonical-agnostic
);

Concretely:

  1. A reader sees opts.url || twice and assumes the second arg is just url. When they notice the fallback differs, they can't tell intentional from typo — nothing signals that canonical hrefs typically strip the query string, so UTMs must deliberately bypass the canonical preference. A comment is needed.

  2. The location alias (lines 75-76) exists specifically to route everything through the canonical preference, and this change makes line 95 the one silent exception reaching past it to window.location. A future cleanup "fixing" it to location.search would reintroduce #259.

  3. Name/arg mismatch: getUtmParamsFromBrowserUrl is now called with either a caller-supplied full URL or a bare ?utm=... search string — neither is necessarily "the browser URL". It works only because the helper grabs everything after the first ?. utils.ts also already has two near-identical helpers (getUtmParamsFromUrl, getUtmParamsFromBrowserUrl); this call site passing a full URL in one branch deepens the ambiguity of which is canonical.

Suggested change (behavior-identical)

// Canonical hrefs typically omit the query string, so UTM params must come
// from the URL the visitor actually landed on, not the canonical URL.
const utmSource = opts.url || window.location.search;
const utmParams = getUtmParamsFromBrowserUrl(utmSource);

Plus:

  • Rename the helper (e.g. extractUtmParams) so the contract reads as "full URL or search string" — it already handles both. Document it in the JSDoc.
  • Add a unit test in src/shared/__tests__/utils.test.ts for the bare search-string shape ("?utm_source=google"), which is the new call contract but is currently only covered by the integration test.
  • Optionally: a unit test in track.spec.ts for canonical-present + UTM-in-location. The suite mocks querySelector to return null today, so the canonical interaction is only covered by the integration test.

Non-blocking FYI

A visit with only utm_source=google will now be recorded both as a referrer (the referrerParams fallback at track.ts:52-59) and as us=google. That's pre-existing semantics, but this fix amplifies it — worth a follow-up thought.

Rename getUtmParamsFromBrowserUrl to extractUtmParams and document that
it accepts a full URL, a path, or a bare search string. Add unit tests
for the bare search-string shape and for canonical-present + UTM-in-location.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuQyKBiQGy2qNMSv3pxdAP
@jkseppan
jkseppan requested a review from stordahl September 6, 2026 14:03

@stordahl stordahl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @jkseppan!

@stordahl
stordahl merged commit fe8f395 into benvinegar:main Sep 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: tracker drops utm query parameters on sites with a canonical link

2 participants