@usereelay/browser
Reelay's browser SDK: error monitoring, breadcrumbs, session replay, and frontend-to-backend trace stitching for web applications.
- No peer setup. Production dependencies are bundled with the SDK: transport, Google Web Vitals, Akamai Boomerang, and DOM session replay.
- Zero host disruption. Every listener and public API runs inside a
defensive boundary. SDK failures go to the
debughook, never your app. - PII never leaves the page. Secrets are redacted client-side, before any payload is sent.
Install
npm install @usereelay/browser
Or use it directly without a bundler:
<script type="module">
import * as Reelay from '/node_modules/@usereelay/browser/dist/index.js';
Reelay.init({ endpoint: 'https://ingest.customer.example', nodeId: 'node_...', ingestKey: 'rk_...' });
</script>
Quick start
Call init() once, before your app boots, so errors thrown during
initialisation are captured too.
import * as Reelay from '@usereelay/browser';
Reelay.init({
endpoint: 'https://ingest.customer.example',
nodeId: 'node_…',
ingestKey: 'rk_…',
});
After init():
- Uncaught errors and unhandled promise rejections are automatically captured.
- Clicks, console errors, and fetch requests are recorded as breadcrumbs.
- A masked DOM session replay is recorded and shipped on error.
- Outgoing fetch requests to same-origin URLs get
traceparentheaders so backend errors link back to this session.
Verify your setup
Throw a test error to confirm everything is wired correctly:
<button onclick="throw new Error('test error')">Test Reelay</button>
The error should appear in your Reelay dashboard within seconds.
Automatic instrumentation
After init(), the SDK instruments the browser automatically; no further
calls are needed.
Global errors
window error and unhandledrejection events are captured via
addEventListener (never by overwriting window.onerror). Other error
monitoring tools on the page continue to work.
Error names and stack traces are parsed client-side.
The SDK parses V8 (Chrome/Edge/Node), SpiderMonkey (Firefox), and JavaScriptCore (Safari) stack traces. Frames from `node_modules`, vendor scripts, and browser internal paths are marked `in_app: false` so the dashboard groups errors by your application code only.Symbolication requires source maps; see Source maps below.
Breadcrumbs
Three types of breadcrumbs are recorded automatically:
| Source | What's captured | Example |
|---|---|---|
| Clicks | Element tag + ID + up to 2 classes (no text content) | button.add-to-cart |
| Console | console.error and console.warn messages (capped at 300 chars) |
Error: something broke |
| Fetch | HTTP method, URL, and response status | POST /api/checkout → 500 |
Breadcrumbs are stored in a ring buffer of the latest 50 entries. They are attached to every error event sent to Reelay.
Session replay
A masked DOM session replay is recorded automatically. It uses rrweb under the hood and operates in error-buffered mode: the last 30–60 seconds of session activity are kept in memory and shipped only when an error is captured. No replay is sent for pages that never error.
Privacy defaults:
- All
<input>values are masked (set to*before serialization). - All text content is masked when
maskAllText(defaulttrue) is enabled. - CSS, fonts, and images are inlined so replays render independently of your live site.
Replay data is sent to {endpoint}/api/ingest/sessions alongside the error
event. The dashboard reconstructs the clip using a shared session identifier,
so each incident gets its own replay video.
Note: Session replay can be disabled with
replay: false. Text masking can be disabled withmaskAllText: false(not recommended for PII safety).
Trace stitching
When your frontend makes a fetch request to a server instrumented with
@usereelay/node, the browser SDK automatically adds two headers:
traceparent: the W3C trace context header, so the backend error shares the sametrace_idas the frontend session.reelay-session-id: the browser session identifier, so the backend error links to the frontend replay.
By default, these headers are only sent to same-origin URLs. You can allow-list
additional origins with tracePropagationTargets:
Reelay.init({
endpoint: 'https://ingest.customer.example',
nodeId: 'node_…',
ingestKey: 'rk_…',
tracePropagationTargets: [
'https://api.acme.com',
/^https:\/\/.*\.internal\.acme\.com/,
],
});
Performance & Core Web Vitals
Set tracesSampleRate above 0 to record a share of page views for
performance. Collection is automatic; there is no manual span API in the
browser. For the sampled share, the SDK records:
- Page-load and navigation transactions: how long pages take to load and route, shown on the dashboard's Performance page.
- Core Web Vitals: LCP, INP, CLS, FCP, and TTFB, shown on the Web Vitals tab.
Reelay.init({
endpoint: 'https://ingest.customer.example',
nodeId: 'node_…',
ingestKey: 'rk_…',
tracesSampleRate: 0.2, // record 20% of page views
});
Core Web Vitals are measured with Google's maintained web-vitals library,
using the stable web-vitals/attribution build. Alongside each metric, Reelay
records Google's diagnostic breakdown (for example LCP load/render phases,
INP input/processing/presentation phases and longest script, and CLS target).
Akamai Boomerang is enabled for the same sampled page views and adds mature
browser diagnostics beyond Web Vitals: navigation and resource waterfalls,
XHR timing, long-task/continuity, paint/event timing, memory/mobile,
compression, and back-forward-cache behavior. Reelay deliberately does not
load Boomerang's Errors, History/SPA, or fetch monitoring because the SDK
already instruments those paths. Set browserPerformance: false to retain
Google Web Vitals while disabling the additional Boomerang diagnostics.
Vitals are collected passively as the page runs and flushed with the page-load
transaction, so a single init() is all that's needed.
Release identity and exact commit attribution
Browsers cannot read a server's runtime environment, so the frontend build must embed its release and, when available, the full source SHA. For Next.js:
NEXT_PUBLIC_REELAY_RELEASE="web@2.4.0" \
NEXT_PUBLIC_REELAY_COMMIT_SHA="<full-sha>" \
npm run build
Reelay.init({
endpoint,
token,
release: process.env.NEXT_PUBLIC_REELAY_RELEASE,
commitSha: process.env.NEXT_PUBLIC_REELAY_COMMIT_SHA,
});
The SDK stamps both release and commit_sha on every error, transaction,
profile, replay, and Boomerang beacon. If release is omitted it falls back to
the commit SHA. Both values must be embedded during the browser build; changing
the web server environment afterward cannot rewrite an existing bundle.
A named release still produces useful release history without a commit. Repository-backed code analysis additionally requires the node to be linked to its repository and the event to resolve to a full commit. Reelay never guesses repository HEAD.
Release names are trimmed, limited to 200 characters, and cannot be ., ..,
or contain tabs, newlines, /, or \. Reusing one release with different
commits is reported as a conflict rather than silently remapped.
Profiling (flamegraphs)
Set profiling: true to capture JS self-profiles that power the flamegraph and
"busiest functions" view. This uses the browser's JS Self-Profiling API,
which is Chromium-only and requires your server to send the
Document-Policy: js-profiling response header on the document. Without both,
profiling is silently inactive: no errors, just no profile data.
Manual error capture
captureException(err)
Report a handled error from a try/catch block:
try {
riskyOperation();
} catch (err) {
Reelay.captureException(err);
}
Returns the event ID (a string like evt_...) or '' if the event was dropped.
captureMessage(message)
Report a plain string message as an error event (useful for debugging):
Reelay.captureMessage('Checkout flow completed');
A synthetic Error is created internally with name = 'Message'.
PII scrubbing
The SDK scrubs sensitive data before any payload leaves the page. Scrubbing runs on error messages, breadcrumb messages, HTTP context objects, and timeline data. It never touches stack trace frames (file paths, line numbers) or event metadata (timestamps, session IDs).
The scrubbing has two layers:
Layer 1: Built-in default patterns
The following patterns are always applied to every string value:
- Bearer/token values:
bearer: xyz,token=abc,api_key: xxx,password: hunter2 - JWTs: any string matching the
eyJ...pattern - Credit card numbers: 13–19 digit sequences (with or without separators)
- Email addresses:
user@example.com
These are always active. There is no way to disable them; if there's a false
positive, use beforeSend to restore the value on the event object.
Layer 2: Sensitive keys
When scrubbing an object, any key matching the following list has its value
replaced entirely with [redacted], regardless of what the value looks like:
authorization, cookie, set-cookie, x-api-key, x-reelay-token,
password, passwd, secret, token, access_token, refresh_token,
credit_card, card_number, cvv, ssn
(Match is case-insensitive.)
Layer 3: User-configured redaction
Every string value in the payload is run through all regex patterns. You can
add custom patterns for your application's secret formats with scrubPatterns,
on top of the always-on built-ins:
// Stripe live keys can appear in log messages or HTTP headers:
Reelay.init({
endpoint: '...',
token: '...',
scrubPatterns: [/sk_live_[A-Za-z0-9]+/g],
});
Matching substrings are replaced with [redacted].
Layer 4: beforeSend
For total control before the event is sent, use beforeSend:
Reelay.init({
endpoint: '...',
token: '...',
beforeSend: (event) => {
if (event.exception.value.includes('sensitive')) return null; // drop
return event;
},
});
Configuration reference
init(options)
| Option | Type | Default | Description |
|---|---|---|---|
endpoint |
string |
— | Required. Customer-owned HTTPS ingestion URL. |
nodeId |
string |
— | Required. Target self-hosted node. |
ingestKey |
string |
— | Required. Key validated by Cloud for every event. |
token |
string |
— | Required. Node API key (rlyt_live_...). Never a ReelayID. |
release |
string |
commitSha |
Artifact version embedded at build time, e.g. web@2.4.0. |
commitSha |
string |
— | Full Git SHA of the frontend artifact. CI should expose it as a public build variable and pass it once to init; short/invalid SHAs are ignored. |
sampleRate |
number (0–1) |
1 |
Fraction of errors to send. 1 = every error. Lower only to reduce volume on high-traffic apps. |
tracesSampleRate |
number (0–1) |
0 |
Fraction of page views recorded for performance: page-load / navigation transactions and Core Web Vitals. 0 = performance monitoring off. Start at 0.1–0.2 in production. |
browserPerformance |
boolean |
true when tracing |
Enable Akamai Boomerang's additional browser diagnostics for sampled page views. |
profiling |
boolean |
false |
Enables JS self-profiling (flamegraphs). Chromium only, and requires the Document-Policy: js-profiling response header; silently inactive otherwise. |
beforeSend |
(event) => event | null |
— | Mutate or veto (return null) an event just before it is sent. Runs after PII scrubbing. |
scrubPatterns |
RegExp[] |
[] |
Extra regex patterns applied to all string values. Matches are replaced with [redacted]. |
maxQueueSize |
number |
30 |
Deprecated compatibility option; bursts remain queued until delivery. |
tracePropagationTargets |
(string | RegExp)[] |
— | Origins (strings) or URL patterns (regexes) that receive W3C traceparent headers. Same-origin only when not set. |
replay |
boolean |
true |
Enable DOM session replay. Disable if you don't need replay or want to avoid the rrweb dependency. |
maskAllText |
boolean |
true |
Mask all text content in replay. Disabling exposes page text, only do this on pages without PII. |
debug |
(msg, detail?) => void |
— | Receive SDK-internal diagnostics. Never logs to console by default. |
API reference
Exported functions
import * as Reelay from '@usereelay/browser';
| Function | Signature | Description |
|---|---|---|
init |
(options: BrowserOptions) => BrowserClient |
Initializes the singleton client and installs all instrumentation. Idempotent; subsequent calls return the existing client. |
getClient |
() => BrowserClient | undefined |
Returns the active client, or undefined if init() hasn't been called. |
captureException |
(err: unknown) => string |
Reports an error through the active client. Returns the event ID ('' if dropped). |
captureMessage |
(message: string) => string |
Reports a string message as an error event. |
Exported types
import type { CoreOptions, ErrorEventPayload, Breadcrumb } from '@usereelay/browser';
| Type | Description |
|---|---|
BrowserOptions |
Extends CoreOptions (which includes tracesSampleRate and profiling) with tracePropagationTargets, replay, and maskAllText. |
CoreOptions |
Options shared by all Reelay SDKs (browser and Node). |
ErrorEventPayload |
The event object delivered to the ingest API. Useful for type-safe beforeSend handlers. |
Breadcrumb |
Shape of a breadcrumb object. |
BrowserClient methods
const client = Reelay.init({ ... });
| Method | Description |
|---|---|
client.sessionId |
The browser session identifier (sess_...), readable at any time. |
client.addBreadcrumb(crumb) |
Manually add a breadcrumb. Scrubbed and bounded (latest 50 kept). |
client.capture(err, mechanism, trace?) |
Capture an error with an explicit mechanism string and optional trace context. |
client.captureMessage(message, ctx?) |
Capture a string as an error, optionally with context. |
client.flush() |
Drain the pending event queue. Returns a promise. |
client.uninstall() |
Remove all instrumentation and tear down the SDK. |
Source maps
Production frontend bundles are minified, so raw stack frames point at hashed
files like assets/index-a1b2c3.js:1:54023. To see original file names, line
numbers, and function names in the Reelay dashboard, you need to upload your
source maps.
The SDK ships a CLI tool for this:
npx reelay-sourcemaps inject-and-upload ./dist \
--url https://api.reelay.app \
--token "$REELAY_TOKEN" \
--delete
How it works:
- Inject debug IDs: stamps each JS chunk with a content-derived debug ID, stored in both the chunk (a tiny runtime snippet) and its source map.
- Upload: sends the map to Reelay's ingestion endpoint, keyed by debug ID.
--delete: removes.mapfiles after upload so they never reach your CDN.
At capture time, the SDK reads the debug ID registry (the runtime snippet injected in step 1) and stamps each stack frame with its bundle's debug ID. The backend resolves the exact map using this ID, regardless of CDN hashing or release naming.
Compatibility: The SDK also reads
_sentryDebugIdsregistries. If you already use a Sentry bundler plugin, your existing debug IDs are picked up with no extra tooling.
Engineering guarantees
Zero host disruption
- Every public API and event listener runs inside a
try/catchboundary. - SDK failures are silently reported to the
debughook, never thrown into the host application. fetchwrapping preserves the original function's exact arguments, return type, andthisbinding. If instrumentation fails, the call degrades to a transparent passthrough.- Click listeners are passive (they never block the UI thread) and
capture-phase (they see every click before any handler's
stopPropagation).
Performance
- Session replay serialization runs in
requestIdleCallback: it never competes with first paint or user interaction. - Breadcrumbs are an O(1) ring buffer with zero allocation on the hot path.
- The transport queue is non-blocking:
send()queues every event in a burst and schedules an async drain. Pending events consume memory while delivery falls behind.
Bounded memory
| Resource | Cap |
|---|---|
| Breadcrumbs | 50 entries (ring buffer) |
| Transport queue | Grows while delivery falls behind; no overflow eviction |
| Replay delta buffer | 2 checkout segments (~60s), 8,000 events each |
| Traced request map | 100 entries |
Data safety on navigation
When the page becomes hidden (visibilitychange), pending events are flushed
using keepalive fetch, which survives page unload. Replay chunks are flushed
similarly for payloads under the keepalive budget.
Development
npm install
npm run build # tsup: ESM + CJS + type declarations
npm test # vitest
npm run typecheck # tsc --noEmit
See ../reelay-test-project/frontend for a runnable test page wired up with
this SDK, including buttons that trigger every class of frontend error.
Ingestion edge contract
SDK requests use POST, Content-Type: application/json, X-Reelay-Token,
and X-Reelay-Node-ID. The backend accepts only the current endpoint schema;
unknown fields and the old X-Reelay-Ingest-Key header are rejected. Rebuild
and repack the SDK after source changes so deployed applications use the same
contract as the source.
The production ALB-associated WAF applies one shared 1,000 requests/minute
budget across all customers and routes. Edge throttles return 429 with
Retry-After: 60; the transport backs off with a retry queue. This is
AWS's approximate rate protection, not an exact counter or a per-token budget.