The site knows about GTM. GTM knows about Analytics.
The site knows one thing, and it is not Analytics.
Here is the whole architecture, and then the part of it I want to argue with:
jwogrady.com
↓
Google Tag Manager GTM-KN54MPZW
↓
GA4 Google Tag G-921NHLGWJK
↓
GA4 Web Stream
↕
Google Search Console
One arrow at the top. The website knows about the tag manager and stops there. It has never heard of Analytics.
That isn’t a figure of speech. The measurement ID the site reports to —
G-921NHLGWJK — appears nowhere in the repository:
$ grep -rn 'G-921NHLGWJK' . --exclude-dir=.git --exclude-dir=public
$ echo $?
1
And nowhere on the page a visitor actually gets:
GA4 id on the live page: 0
gtag/js loaded directly: 0
GTM container on the page: 2
Two references to a tag manager. Zero to the analytics product that is currently counting every one of these words being read.
The obvious way, which we didn’t take
Every tutorial for putting analytics on a site says: copy the gtag.js
snippet, paste it into your template. It works, it takes four minutes, and
it is what I would have done a year ago without thinking.
The alternative is to install only the tag manager, and then teach that
about Analytics — a Google Tag named GA4 – Google Tag – All Pages, tag ID
G-921NHLGWJK, on the Initialization – All Pages trigger. The site gets
one integration. The analytics configuration lives one layer away, in a
product built for holding it.
The site knows one thing, and it is not Analytics.
The payoff is the bit that isn’t obvious until you need it. Swap analytics vendors and the website does not change — no commit, no deploy, no template edit, no risk of breaking a build over a marketing decision. Add a conversion pixel, a heatmap, a consent banner: the site still does not change. Whatever else those things are, they are no longer your codebase’s problem.
Does it actually work
Everything above is architecture, and architecture is easy to be pleased with.
The question is whether a page_view actually arrives.
It does. Tag Assistant connects, GTM loads, GA4 – Google Tag – All Pages
fires, the GA4 tag executes, and a real page_view reaches G-921NHLGWJK
with no errors. Enhanced Measurement initialises alongside it — scrolls,
outbound clicks, file downloads, site search, video engagement.
I want to flag what kind of claim that is, because it is a different kind
from the rest of this entry. Every other statement here I checked from a
terminal: the grep, the absent measurement ID, the two references on the
page. This one I could not. Whether a tag fires is a fact about a browser
executing JavaScript, and no amount of curl will tell you. Somebody had to
open the site and watch.
That is worth knowing in general. A static check can prove a script is on the page. Only a browser can prove it ran.
Now the part I have to be honest about
This morning, on this same site, I wrote up a bug where the production build took its canonical hostname from an environment variable instead of a file in Git, and the conclusion was firm: the reviewed file decides, not the control panel. That entry is a few hours old.
And then I deliberately installed a control panel into every page on the site.
I want to sit in that rather than smooth it over, because a principle you only apply when it’s convenient isn’t a principle, it’s a mood.
Here is the distinction I think actually holds. baseURL is a fact about
what the site is. It changes approximately never, it must be reproducible
from the repository alone, and a silent change to it corrupts every canonical
tag on the site. That belongs in a reviewed file and nowhere else.
Tag configuration is an operational decision that changes weekly and belongs to whoever is running the marketing, who should not need a deploy — or a developer — to add a pixel. Routing that through Git doesn’t make it safer, it makes it slower, and slow enough that people stop doing it properly and start pasting things in a hurry.
Different classes of decision. Different homes. That’s a real distinction and I believe it.
But it is not a free win, and here’s the bill. GTM can inject arbitrary JavaScript into every page of this site, and nothing in my repository, my review process or my build will ever see it. The guards I wrote today compare hashes of theme files; they have no opinion whatsoever about a script that arrives at runtime from a console I clicked once. I have traded a piece of reproducibility for a piece of agility, on purpose, with my eyes open — and anybody who installs a tag manager without noticing they made that trade has made it anyway.
The other half, and a callback
Search Console is linked to the GA4 stream, and the property is a Domain
property for jwogrady.com rather than a URL-prefix property for
https://www.jwogrady.com.
That distinction is the one from
an earlier entry today,
wearing different clothes: ownership is a claim about a domain, serving is
a job done by a hostname. The TXT record on the bare domain proves who owns
it. The www hostname does the serving. A Domain property is verified by the
former and covers everything under it, including a www I might retire and a
subdomain I haven’t invented yet.
A URL-prefix property would have covered exactly one hostname, on a day spent making sure two of them agreed. It would have worked, and it would have shown me half my site forever.
What this is, outside software
Put the thing that changes often behind a boundary the durable thing doesn’t know about.
That’s the whole pattern, and once you see it you find it everywhere in a business that’s been run well:
- The number printed on ten thousand flyers is a forwarding number, not a desk. You change who answers without reprinting anything.
- The email on the contract is
accounts@, not a person who might leave. - The supplier named in the process document is a role — “our packaging supplier” — and which company that is lives in one place you can change.
And the same honest caveat applies every time: every layer of indirection buys you flexibility and sells a piece of your ability to know what’s happening by reading the durable thing. The flyer no longer tells you who picks up. The repository no longer tells you what scripts run on your site.
Buy it anyway, usually. Just know you bought it, and know where to go and look when the answer stops being obvious.
The pattern carries forward to the organization side of this — the same shape
should hold for hugo-pulse, where the sites are clients’ and the thing that
changes weekly is somebody else’s marketing. If the site knows one thing, a
client can be handed the layer above it without ever being handed the repo.