Install Browser RUM

Add Edge Delta Real User Monitoring with a script tag or the npm package, verify data is flowing, and lock the ingestion token to your origins.

Overview

You can install RUM two ways: a script tag that loads the SDK from Edge Delta’s CDN, or the @edgedelta/browser-rum npm package bundled into your application. Both behave identically at runtime and need only two values, an ingestion token and a service name. They differ in one thing: what happens to errors thrown before RUM is running.

Script tagBundler (npm)
InstallSave the loader file to your static assetsnpm install @edgedelta/browser-rum
Errors thrown before your bundle loadsCapturedLost, unless the Vite plugin adds the loader
Configuration sourceHand-written in the loader fileResolved at build time from environment variables
Best forPlain sites and any app whose HTML you controlVite applications

Prefer the script tag for a plain site or any app whose HTML you control. Prefer the npm package for a Vite app: its plugin emits the same early-error loader, so you give up nothing.

Prerequisites

Before installing, make sure you have the following:

  • An Edge Delta ingestion pipeline. RUM sends data directly to the pipeline’s HTTP ingestion endpoints, so no agent is required.
  • For the script tag: the ability to serve a static file from your own domain and edit the document <head>.
  • For the npm package: a bundler. The build-time plugin supports Vite.

Get an ingestion token

The RUM token is the ingestion token of the pipeline that will receive the traffic:

  1. Click Pipelines and create an ingestion pipeline, or open an existing one. See Ingestion Pipelines for the creation steps.
  2. Copy the ingestion token shown with the pipeline’s endpoints.
Ingestion pipeline endpoints with their authentication tokens Ingestion pipeline endpoints with their authentication tokens

Note: The token is public by design. It ships in your page source and anyone can read it. That is expected: it grants nothing but the ability to send data. Lock it to your own sites with an origin allowlist (described in this topic) rather than trying to hide it. Never put an Edge Delta API token in a web page.

Install with a script tag

Serve the loader from your own domain

Save the following loader as a file wherever your site serves static assets, such as public/rum.js or static/rum.js, and replace YOUR_PUBLIC_TOKEN with your ingestion token and your-app with a name for this application:

"use strict";(function(){(function(t,d,u){var n=t.EdgeDelta=t.EdgeDelta||{};if(n.__loaded)return;n.__loaded=1,n.q=n.q||[],n.e=n.e||[];var l=20;function i(e,r,g){n.e.length<l&&n.e.push([String(e),String(r),g])}t.addEventListener("error",function(e){i(e.message,e.error&&e.error.name||"Error",e.error&&e.error.stack)}),t.addEventListener("unhandledrejection",function(e){var r=e.reason;i(r&&r.message?r.message:String(r),r&&r.name?r.name:"UnhandledRejection",r&&r.stack)});for(var c=["init","setContext","setUser","addError","addMessage","addBreadcrumb","startView","endView","flush","use"],o=0;o<c.length;o++)(function(e){n[e]=function(){n.q.push([e,[].slice.call(arguments)])}})(c[o]);var a=d.createElement("script");a.src=u,a.async=1,a.crossOrigin="anonymous";var s=d.getElementsByTagName("script")[0];s&&s.parentNode?s.parentNode.insertBefore(a,s):d.head.appendChild(a)})(window,document,"https://js.edgedelta.com/rum/v0.6.1/rum.min.js");})();
EdgeDelta.init({ token: "YOUR_PUBLIC_TOKEN", service: "your-app" });

Load it first in the head

Reference the file from <head>:

<!-- Edge Delta RUM -->
<script src="/rum.js"></script>

Three rules apply, and each one broken costs you the early-error window:

  • No async, no defer, and nothing above it. The file installs error handlers and starts buffering synchronously, then loads the SDK bundle asynchronously so it never blocks rendering. Everything thrown before the bundle arrives is still captured, which is where the most interesting failures happen.
  • Do not paste the loader inline into the HTML. An inline script needs 'unsafe-inline' or its own hash in a script-src Content Security Policy, and HTML formatters such as Prettier reformat embedded scripts, which silently invalidates that hash and takes RUM down with no error anywhere. A same-origin file needs neither, and init can use RegExp and callback options there.
  • Keep init out of application code. For a framework app the tag belongs in the HTML shell, such as index.html, app.html, or the document template, not in a root component. In Next.js, use app/layout.tsx with <Script src="/rum.js" strategy="beforeInteractive">.

TypeScript for script-tag installs

Types are served beside the bundle at https://js.edgedelta.com/rum/v0.6.1/rum.d.ts. Save the file into the project and reference it once to type both EdgeDelta and window.EdgeDelta:

/// <reference path="./rum.d.ts" />

Install with npm

Install the package and call init from the first module your entry imports:

npm install @edgedelta/browser-rum
import { init } from "@edgedelta/browser-rum";

init({ token: "YOUR_PUBLIC_TOKEN", service: "acme-web", environment: "production" });

Everything after that point is covered. The gap is what the browser threw while your bundle was still being fetched and parsed, which no code inside that bundle can watch for itself. The Vite plugin closes that gap, and init picks up whatever it buffered.

The package has three subpath exports: /react, /vite, and /testing. The react and vite peer dependencies are optional, so installing the core alone pulls in nothing extra. See RUM Configuration and API for the React error reporting and testing helpers.

The Vite plugin

The @edgedelta/browser-rum/vite plugin does the two things that have to happen at build time:

// vite.config.ts
import { edgeDeltaRum } from "@edgedelta/browser-rum/vite";

export default defineConfig({
  plugins: [edgeDeltaRum({ config: { service: "acme-web" } })],
});

It resolves configuration from the environment and serves it as the virtual:edgedelta-rum module, so no variable names and no environment parsing reach the browser bundle:

import { rumConfig } from "virtual:edgedelta-rum";
if (rumConfig) init({ ...rumConfig, ignoreErrors: [/^ResizeObserver/] });

rumConfig is null whenever no token was configured. That is the off switch: nothing is injected and nothing is reported. For its type, reference the client types once from any file with /// <reference types="@edgedelta/browser-rum/vite-client" />.

It emits a small blocking script into <head> that buffers the errors thrown before your bundle runs and hands them to init. It replaces an <!--%EDGEDELTA_RUM%--> comment if the document has one and appends to <head> otherwise; pass placeholder to look for a different comment.

Environment variables

The plugin reads the following variables at build time. Anything the environment does not set falls back to the plugin’s config option, then to the SDK default. false and 0 read as off for the booleans:

VariableConfig field
VITE_RUM_TOKENtoken. Its absence turns everything off.
VITE_RUM_SERVICEservice
VITE_RUM_ENDPOINTendpoint
VITE_RUM_ENVIRONMENT, else VITE_STAGEenvironment
VITE_APP_VERSIONversion
VITE_RUM_SAMPLE_RATEsampleRate
VITE_RUM_REQUEST_SPANSrequestSpans
VITE_RUM_NAVIGATION_SPANSnavigationSpans
VITE_RUM_USE_BEACONuseBeacon
VITE_RUM_DEBUGdebug
VITE_RUM_PROPAGATE_TO, else the host of VITE_API_URLpropagateTo. A value of false disables propagation entirely.

Loader modes

The loader option controls what the plugin puts in <head>:

loaderWhat lands in the headCost
true (default)A <script src> to a hashed same-origin assetOne extra request, cached forever, allowed by script-src 'self'
'inline'The loader source itselfNo request; needs 'unsafe-inline' or a sha256- hash of those bytes in your CSP
falseNothingNo early-error buffer unless you serve LOADER_SOURCE yourself

If your CSP uses a nonce instead of hashes, set Vite’s own html.cspNonce: Vite appends it to every script tag after plugins run, so the loader is covered in both modes with nothing to configure here. loader: false is for an app whose HTML is assembled outside Vite; you still get virtual:edgedelta-rum, and placing the loader is yours to do.

Verify the installation

Confirm data is flowing before rolling out further:

  1. Load your site in a browser and interact with the page.
  2. Switch tabs or close the page. The page load span is sent when the page is hidden, so a tab you never switch away from has not sent it yet.
  3. In the browser’s developer tools, confirm a POST to /otel/v1/traces succeeded. Errors post to /otel/v1/logs within a few seconds of being thrown.
  4. In Edge Delta, open the Trace Explorer and filter for your service.name value. Page views appear as documentLoad spans, and route changes in a single page application appear as routeChange spans. Errors appear in Log Search.
Trace Explorer filtered to a RUM pipeline showing page load traces with request and latency charts Trace Explorer filtered to a RUM pipeline showing page load traces with request and latency charts

Tip: Set debug: true, or add an ed_rum_debug key to the browser’s localStorage, to log the SDK’s whole pipeline to the console while you verify. See Troubleshooting RUM.

Restrict the token to your origins

Because the token is public, bind it to the origins that may use it. Set allowed_origins on the pipeline’s HTTP ingestion node so a copied token cannot be used from anywhere else:

nodes:
  - name: rum_ingest
    type: http_ingestion_input
    allowed_origins:
      - https://acme.com
      - https://*.acme.com

The matching rules are strict:

  • A wildcard must be the whole leading host label. https://*.acme.com matches https://app.acme.com and nested subdomains such as https://a.b.acme.com, but not the apex domain https://acme.com (list it separately) and not lookalikes such as https://evilacme.com.
  • Schemes and ports must match exactly.
  • An empty list means any origin is accepted.

Setting allowed_origins makes the token browser-only: requests without an Origin header are rejected, because that is what a stolen token replayed from a script or a server looks like.

Warning: Do not set allowed_origins on a node that also receives server-side traffic. Server requests carry no Origin header and would be rejected. Use a separate ingestion node or pipeline for RUM.

Pin the bundle with Subresource Integrity

For script-tag installs, optionally add a Subresource Integrity (SRI) hash so the browser refuses the bundle if its bytes ever change. This prevents a compromised CDN from running new code on your pages.

Compute the hash of the exact file the loader fetches:

curl --fail -sS https://js.edgedelta.com/rum/v0.6.1/rum.min.js | openssl dgst -sha384 -binary | openssl base64 -A

Then edit the injected script inside the loader file. Change:

a.crossOrigin="anonymous"

to:

a.crossOrigin="anonymous",a.integrity="sha384-PASTE_HASH_HERE"

The variable prefix is whatever the loader names the script element, so keep the prefix already there. Published versions are immutable, so a hash stays valid for the life of the version you pinned.

Note: Only pin a fully versioned URL, such as one ending in /rum/v0.6.1/. A rolling major-version URL deliberately serves a new build on every patch release, so a pinned hash would begin rejecting it and RUM would go silent with no warning.

After adding the hash, load the page and confirm rum.min.js fetched with status 200. A wrong hash makes the browser block the script entirely and log an SRI error to the console.

Content Security Policy

If the site sets a Content Security Policy, two directives matter:

DirectiveNeeds
script-srchttps://js.edgedelta.com for the SDK bundle. The loader file is same-origin, so 'self' already covers it, which is the reason for not inlining it.
connect-srcYour endpoint host.

Either block shows up as a CSP violation in the browser console and as nothing at all in Edge Delta.

See Also