Install
There are two ways to install. Pick one here and the rest of these docs follow your choice — every code sample on every page switches with the tabs below.
Install the package:
npm install @ritim/browser-sdkThen start it once, on the client:
import { init } from '@ritim/browser-sdk';
init({ projectKey: 'P-ABC123' });That call has to run in the browser, after the page loads. Where exactly it goes depends on your framework — the two common ones have a page each:
Anywhere else, put it in whatever runs once after your app mounts.
One tag in <head>, no code and no build step:
<script async src="https://cdn.ritim.io/latest/rum.min.js" data-project-key="P-ABC123" data-release="a1b2c3d"></script>data-project-key is what starts it: on load the SDK finds the tag that loaded
it and begins if that tag carries a project key. There is no init() call, no
router plugin and nothing to run per navigation.
async is deliberate — the SDK must never block the page it is measuring. It
reads buffered performance entries to recover what happened before it started,
so loading late costs very little.
The project key is public. It identifies a project; it does not authorise anything, and it is meant to be visible in your page source.
Which one to pick
Section titled “Which one to pick”You are on the npm path. It is the right choice when you already have a build step, because it gives you:
- Type definitions, so
init()is checked at compile time. - A version you pin and upgrade deliberately, rather than one that changes under you when the CDN file is replaced.
- One fewer network request, since the SDK is in a bundle you already ship.
- A release identifier you can pass from your build, which is the part that turns a regression into a diff.
The cost is that the SDK is in your bundle: if it fails to build, your build fails. On the script tag path that is impossible.
You are on the script tag path. It is the right choice when you want the install to be one line and nothing else, because:
- It works on any page, including ones with no build step at all.
- It cannot break your build, because it is not in it.
- If the file fails to load, the page it measures is unaffected.
The cost is that you get no type checking, and the file at latest/ is replaced
when we ship — you pick up changes on your next cache revalidation rather than
when you choose to.
If you have a bundler, prefer npm.
What you get without doing anything else
Section titled “What you get without doing anything else”Neither path needs integration code for any of this:
Navigations are detected by the SDK. It wraps history.pushState and
replaceState, and listens for popstate, hashchange, the Navigation API’s
currententrychange, and bfcache restores. One measurement per navigation,
whatever framework the page uses — or none at all.
Measurements send themselves roughly two seconds after the page settles, not when the visitor leaves. Page hide is only a backstop.
INP is its own event, sharing the page view’s measurementId. It travels in
the page view’s request when the visitor interacted early, and in a request of
its own when a worse interaction turns up later.
The one case you have to report yourself
Section titled “The one case you have to report yourself”reportSoftNavigation() exists for view changes that never touch the URL — a
modal, or a tab your product counts as its own view. Hash-router apps need it
too, because a hash change is deliberately not treated as a navigation.
import { reportSoftNavigation } from '@ritim/browser-sdk';
reportSoftNavigation();window.RumMonitor.reportSoftNavigation();Runtime API
Section titled “Runtime API”import { init, setRelease, reportSoftNavigation, SDK_VERSION } from '@ritim/browser-sdk';| Export | Purpose |
|---|---|
init(options) |
Start the SDK. Later calls are ignored. |
setRelease(release) |
Set the release for subsequent measurements. |
reportSoftNavigation() |
Close the current measurement and start a new one. |
SDK_VERSION |
The build identifier of the SDK itself. |
RumInitOptions is exported as a type. Every field is in
Configuration.
The tag defines one global, window.RumMonitor:
interface RumMonitorApi { init(options: RumInitOptions): void; setRelease(release: string): void; reportSoftNavigation(): void; readonly version: string;}Because the tag is async, window.RumMonitor does not exist until the bundle
executes. If you need to call it, wait for load:
<script async src="https://cdn.ritim.io/latest/rum.min.js"></script><script> window.addEventListener('load', () => { window.RumMonitor.init({ projectKey: 'P-ABC123', release: window.__BUILD_ID__, }); });</script>Load the tag without data-project-key when you do this, so auto-init stays
out of the way. Calling init() twice is harmless; the second call is ignored.
Where measurements go
Section titled “Where measurements go”The collector endpoint is compiled into the bundle, not configured on the page — on both paths. There is nowhere in a script tag to supply one, and an inlined constant costs no payload bytes and cannot be tampered with on the page.
init({ endpoint }) and data-endpoint override it, for self-hosted collectors
and tests.
Requests are POSTed with navigator.sendBeacon, falling back to
fetch(…, { keepalive: true }). The body is JSON but labelled text/plain,
which keeps the request CORS-simple and avoids a preflight — sendBeacon cannot
preflight at all, and silently drops requests that would need one.