Analytics & Google Tag ManagerAnalytics in eQuantic.UI is a capability, not a script you paste: a page asks for IAnalytics the way it asks for a camera, and never learns who is listening. The eQuantic.UI.Gtm package is an INSTALLER: one call wires a Google Tag Manager container to that capability, end to end.
Since 0.2.0-preview.29
builder.Services.AddUI(options => options
.ScanAssembly(typeof(Program).Assembly)
That one call installs three things into the HTML shell:
1.
The official container snippet: the same bytes Google documents, parameterized only where GTM itself templates them.
2.
The installer declaration (window.__EQ_ANALYTICS__): what arms the runtime's IAnalytics realization. Without an installer, tracking is a silent no-op by design.
3.
SPA page views: the client router announces every committed navigation (eq:navigate), and the shell turns it into page_view pushes carrying page_path and page_title, GA4's own field names. The container sees the initial load by itself; these are the navigations it cannot see.
The container id is validated at startup: a typo'd id installs a container that silently collects nothing, and that is discovered in next month's empty report, so refusing early is the kinder failure.
public override VisualNode Build(ComponentContext context)
_analytics = context.GetService<IAnalytics>();
private async Task Submit()
// …after the server said yes:
_analytics?.Track("sign_up");
_analytics?.Track("purchase", new Dictionary<string, object?>
Track is fire-and-forget by contract, because analytics must never make a page wait. Event names are YOUR vocabulary (sign_up, begin_checkout); the framework never invents or prefixes any. On the server the same call is a no-op: SSR is not a user, and a page that tracked during rendering would count its own crawlers.
One container per app, on purpose
A GTM container loads into the DOCUMENT and never unloads, so in a SPA a "per-page container" cannot exist. Per-route variation is what the container's own triggers are for: the automatic page_view carries page_path precisely so marketing can fire tags per route without the app changing, which is the entire point of a tag manager.
What does exist is the agency-plus-client case: call UseGtm once per container. The second call adds only its snippet; both ride the same dataLayer (GTM's own multi-container rule, enforced at startup).
.UseGtm("GTM-XXXXXXX", gtm => gtm
.WithDataLayerName("eqData") // when another script already owns `dataLayer`
.WithoutSpaPageViews() // container uses GA4's history trigger instead
.WithEnvironment("auth…", "env-9")) // GTM environments (gtm_auth / gtm_preview)
Turn WithoutSpaPageViews() on when the container tracks history changes itself, or the same navigation counts twice.
What is deliberately absent
•
The <noscript> iframe from Google's install instructions. It measures users whose browsers run no JavaScript, and such a user gets no app at all here: there is nothing to measure.
•
Consent Mode helpers. Consent is a product decision with legal weight; v1 does not wrap it. A consent banner built with the SDK can push consent updates as ordinary dataLayer events through Track in the meantime.
•
A native realization. IAnalytics resolves to a no-op in a Photon window today; the mobile analytics bridges join with the native track.
•
Capabilities: the ask-by-interface pattern IAnalytics follows.