Overview
The OpenTelemetry document load instrumentation traces page loads in your web application. Each load arrives in SigNoz as one trace: a span for the document itself, one span per resource the page fetched, and the DNS lookup, connection, request, response, and DOM processing phases recorded as timed events on those spans.
Those spans answer questions a single page-load number cannot:
- Which resource delayed rendering, and by how much
- Whether a third-party script or external API caused a slow load
- How page load time shifted after a release
This page covers the instrumentation setup and one trace-based alert on resource fetch time.
Prerequisites
- A web application you can add a JavaScript file to
- A SigNoz Cloud account or a self-hosted SigNoz deployment
Setup
Step 1: Install dependencies
npm install @opentelemetry/sdk-trace-web \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/instrumentation \
@opentelemetry/instrumentation-document-load \
@opentelemetry/resourcesStep 2: Create an instrumentation file
In the src directory of your web application, create otel-setup.js and paste the code below.
import { BatchSpanProcessor, WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { resourceFromAttributes } from '@opentelemetry/resources';
const exporter = new OTLPTraceExporter({
// Self-hosted SigNoz: use your collector's OTLP HTTP endpoint instead.
url: 'https://ingest.<region>.signoz.cloud:443/v1/traces',
headers: {
// Not needed for self-hosted SigNoz.
'signoz-ingestion-key': '<your-ingestion-key>',
},
});
const provider = new WebTracerProvider({
resource: resourceFromAttributes({
'service.name': '<service_name>',
}),
spanProcessors: [new BatchSpanProcessor(exporter)],
});
provider.register();
registerInstrumentations({
instrumentations: [new DocumentLoadInstrumentation()],
});Verify these values:
<region>: Your SigNoz Cloud region.<your-ingestion-key>: Your SigNoz ingestion key.<service_name>: The name your application appears under in SigNoz.
Step 3: Import the instrumentation file
Import otel-setup.js as the first import in your entry file, so the instrumentation loads before your application code runs.
import './otel-setup';Step 4: Start the app
From the root folder of your web application, run:
npm startLoad a page in the browser. Your application shows up in the Services section of SigNoz.

Step 5: Read the document load traces
Open the Traces section. Each page load appears as a trace rooted at a documentLoad span, with one documentFetch child for the HTML document and one resourceFetch child per resource the page pulled in.
Those three names are the only spans the instrumentation emits. Open a span to read its phase timings, which arrive as events such as domainLookupStart, connectStart, requestStart, responseStart, and domInteractive. Filters and alerts match on the span names, so query name = resourceFetch rather than a phase name.

Set up an alert on resource fetch time
resourceFetch spans cover the external scripts, images, style sheets, and fonts a page needs to render. Alerting on their duration tells you when to cache or optimize an asset, before users report a slow page.

Define the query
Use the Create an Alert button at the bottom of the Traces section, or start from the Alerts section. For the full options, see trace-based alerts.
On the Traces Based Alerts page, set the Traces filter to service.name = <service_name> and name = resourceFetch. Set Y-axis units to seconds, set Legend Format to Resource Fetch, then press Stage & Run Query.

Define the alert condition
Send a notification when A is above the threshold at least once during the last 5 mins, with Alert Threshold set to 10 seconds.

Set the alert configuration
Set Severity to critical and Alert Name to ResourceFetch. Toggle on Alert all the configured channels, or pick one channel from the dropdown. Save the rule and test the notification.

The rule now shows up in the alert list and reports OK until a resource fetch crosses 10 seconds.

Troubleshooting
Open your browser's developer console and check the POST request to /v1/traces on the Network tab. Its status tells you which case below applies.
Console shows a CORS error and the request never reaches the collector (self-hosted)
- Likely cause: the collector's OTLP HTTP receiver does not allow your frontend's origin, so the browser blocks the request at the preflight stage
- Fix: add your frontend URL under
cors.allowed_originson theotlpreceiver'shttpprotocol and restart the collector, as shown in CORS in OTLP HTTP Receiver - Verify:
curl -i -X OPTIONS -H "Origin: <your-frontend-url>" -H "Access-Control-Request-Method: POST" <your-collector-otlp-http-url>/v1/tracesreturns anAccess-Control-Allow-Originheader. A405 method not allowedresponse means CORS is still off.
Console reports the request was blocked as mixed content (self-hosted)
- Likely cause: an HTTPS page cannot
fetcha plainhttp://collector endpoint. This surfaces after deploying, sincehttp://localhostis exempt and works locally. - Fix: expose the collector over HTTPS, or proxy
/v1/tracesfrom your app's own origin - Verify: the exporter
urlstarts withhttps://, or is a same-origin path, and thePOSTreaches the Network tab with a status instead of failing outright
The request returns 401 or 403 (SigNoz Cloud)
- Likely cause: wrong ingestion key, or a key issued for a different region than the one in the exporter URL
- Fix: copy the key again from your ingestion settings and set
<region>to that same workspace's region - Verify: the
POSTtohttps://ingest.<region>.signoz.cloud:443/v1/tracesreturns200
TypeError: provider.addSpanProcessor is not a function
- Likely cause: the instrumentation file was written for OpenTelemetry JS SDK 1.x, where processors were added after construction
- Fix: pass processors through the constructor instead:
new WebTracerProvider({ spanProcessors: [new BatchSpanProcessor(exporter)] }) - Verify: the page loads without console errors and a
POSTto/v1/tracesappears on the Network tab
Resource is not a constructor, or Resource is missing from @opentelemetry/resources
- Likely cause: SDK 2.x removed the
Resourceclass - Fix: build the resource with
resourceFromAttributes({ 'service.name': '<service_name>' }) - Verify: spans in SigNoz carry your
service.nameinstead ofunknown_service
The POST returns 200 but nothing appears in SigNoz for a few seconds
- Expected:
BatchSpanProcessorbatches spans and exports every 5 seconds by default. In the browser it also flushes when the visitor hides the tab or navigates away. - Fix: wait one batch interval, then refresh the Traces explorer. Widen the time range if you are looking at a narrow window.
A third-party asset has a resourceFetch span, but no breakdown inside it
- Likely cause: the browser withholds phase-level timing for cross-origin resources, so the span keeps its total duration while the request and response timings collapse to zero
- Fix: have the asset's host send a
Timing-Allow-Originheader naming your frontend origin - Verify:
performance.getEntriesByName('<asset-url>')[0]reports non-zerorequestStartandresponseStartin the browser console
Next steps
- Capture frontend web vitals with OpenTelemetry
- Send frontend traces for user interactions and network calls
- Instrument your backend to link frontend and backend traces
Get Help
If you need help with the steps in this topic, please reach out to us on SigNoz Community Slack. If you are a SigNoz Cloud user, please use in product chat support located at the bottom right corner of your SigNoz instance or contact us at cloud-support@signoz.io.