Skip to content

Analytics

Edgar Mesquita edited this page Aug 14, 2026 · 2 revisions

Analytics & Google Tag Manager

🌐 This page in: English · Português

Analytics 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


Install

using eQuantic.UI.Gtm;

builder.Services.AddUI(options => options
    .ScanAssembly(typeof(Program).Assembly)
    .UseGtm("GTM-XXXXXXX"));

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.

Tracking from a page

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?>
    {
        ["value"] = 42,
        ["currency"] = "EUR",
    });
}

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).

Options

.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.

Related

Clone this wiki locally