Transport
The browser script records page views, clicks, web vitals, errors and fetches - and then has to get them
somewhere. options.Browser.Transport decides how: Direct, where the page posts
to your endpoints itself, or Relay, where it hands its records to .NET over JS interop and
the .NET pipeline exports them. The choice is about where the network call happens - and therefore about
CORS and about who can see your exporter credentials.
Auto, Direct and Relay
| Value | What happens |
|---|---|
Auto (default) |
Relay in Blazor Hybrid, Direct everywhere else. The host kind is
detected (or set with options.Host): WebAssembly when running in the browser,
Server when ASP.NET Core hosting is registered, Hybrid otherwise.
|
Direct |
The script exports to the configured endpoints itself, with fetch (and
keepalive when the page is hidden). Works on every page, static SSR included,
and needs no .NET at all once the page is served.
|
Relay |
The script hands its records to .NET over JS interop, and the .NET pipeline exports them with
the same exporters, processors and persistence as its own records. Needs
<BelemetryInitializer />; on Blazor Server the records travel over the
circuit.
|
builder.Services.AddBitBelemetryServices(options =>
{
options.AddOtlpExporter("https://otel.example.com", otlp =>
{
otlp.Headers["x-api-key"] = builder.Configuration["Otel:ApiKey"]!;
});
// Auto: Relay in Hybrid, Direct everywhere else.
// Relay keeps the API key above on the server (Blazor Server) or in the app (Hybrid).
options.Browser.Transport = BelemetryBrowserTransport.Relay;
});Direct: CORS and public endpoints
With Direct, the request to your collector comes from the page's origin, so the receiver must allow
that origin - a browser blocks a cross-origin POST with a Content-Type of
application/x-protobuf or application/json unless the preflight says yes. For
the OpenTelemetry Collector that is the cors block of its HTTP receiver; for the .NET Aspire
dashboard, DASHBOARD__OTLP__CORS__ALLOWEDORIGINS.
The .NET side often reaches the collector over an internal address the browser cannot. Give the OTLP
exporter a BrowserEndpoint - a public, CORS-enabled route - and the script uses that instead of
Endpoint. It may be relative: this site uses /otlp, so the browser always posts
to the origin it was served from, behind any proxy.
Everything the script exports with is in the page. The browser configuration is
written into the HTML by <BelemetryScript />, so an exporter's Headers -
a vendor API key, say - are readable by anyone who opens it. For browser traffic, use a collector
that needs no credentials, set IncludeInBrowser = false on the exporter that carries
them, or switch to Relay. An Azure Monitor exporter with an AccessTokenProvider is never
handed to the script.
Relay: no credentials in the page
With Relay, the page never talks to a collector. Each batch goes to the
Belemetry.Relay callback of <BelemetryInitializer />, and from there into
the same pipeline that exports .NET's own logs and traces - so processors run on browser records too, and
custom exporters (an in-memory viewer, your own IBelemetryExporter) receive them. The user's
consent was already applied in the page, so a relayed record is not filtered a second time.
| Host | Where the relayed records are exported from |
|---|---|
| Blazor Hybrid (the Auto default) | The app process. No CORS - the WebView's origin is not a web origin anyone can allow - and file persistence while offline. |
| Blazor Server, or a Server-rendered page of an Auto app | The server, over the circuit. No CORS and no credentials in the page; the server pipeline does not have to be on for relayed records to be exported. |
| WebAssembly, or a WebAssembly-rendered page of an Auto app | The .NET pipeline running in the browser. The export is still a request from the page, so CORS still applies and the configuration is still downloadable: Relay here buys processors and custom exporters, not secrecy. |
Relay depends on an interactive component. Until <BelemetryInitializer /> attaches,
the script keeps its records queued in memory; a static SSR page that never becomes interactive has
nothing to hand them to. If part of your app is static SSR, keep that traffic on Direct.
This site
The demo runs as a Blazor Web App with the WebAssembly render mode and leaves Transport on
Auto, which resolves to Direct: the script posts OTLP/JSON to
/otlp on this same origin, so there is no CORS to configure and no credential to hide. Send
an event and watch it arrive - it went from the page to the server's receiver with a plain
fetch.
Nothing yet - send an event (it needs the Events feature granted).
API
BelemetryBrowserTransport
Enum
How browser telemetry leaves the page.
| Value | Number | Description |
|---|---|---|
Auto |
0 |
BelemetryBrowserTransport.Relay in Blazor Hybrid, BelemetryBrowserTransport.Direct everywhere else. |
Direct |
1 |
The script exports straight to the configured endpoints with fetch. The endpoints must allow the page origin (CORS). |
Relay |
2 |
The script hands its records to .NET over JS interop and the .NET pipeline exports them. Needs an interactive component (<BelemetryInitializer />); avoids CORS and keeps exporter credentials out of the page. On Blazor Server the records travel over the circuit. |