Browser Monitoring
Real-user monitoring for your frontend. Install a lightweight SDK, collect Core Web Vitals, sessions, JavaScript errors, and network timing from real browsers — then investigate in /app/browser. Observed signals only. No session replay.
Reading time ~8 min · Prerequisites: Pro+ workspace with browser_monitoring · A site you can add a script to
What you get
Browser Monitoring is push-based RUM. Your visitors' browsers send beacons to Mydle. The product never invents sessions, vitals, or error rates.
- Overview — sessions, active users, pageviews, bounce rate, error rate, load time, CWV score, plus browser / device / country distributions
- Performance — LCP, CLS, INP, FCP, TTFB trends and slowest / largest pages
- Sessions — anonymous timelines (Visited → Click → XHR → Error → Closed)
- JavaScript errors — fingerprint groups, occurrences, affected sessions, browsers / versions
- Network — fetch / XHR duration, status, slow and failed requests
- Page analytics — most visited, slowest, highest error / bounce routes
- AI Summary — grounded Observed / Likely / Unknown insights (never invents causation)
Quickstart
From an empty app to a visible session in a few minutes. Replace placeholders with values from /app/browser.
Create a browser app
Open Browser Monitoring, enter an app name (for example
Marketing site), and create. Copy the public key (pk_…).Install the SDK
Add the script below before
</body>(or use a framework snippet in Install).<script src="https://YOUR_MYDLE_ORIGIN/sdk/mydle-browser.js" data-key="pk_YOUR_PUBLIC_KEY" data-endpoint="https://YOUR_MYDLE_ORIGIN/api/rum/v1/ingest" data-release="1.0.0" defer ></script>Load your site
Open a page that includes the script. In Network, confirm a
POSTto/api/rum/v1/ingest(sendBeacon or fetch).Confirm in the dashboard
Refresh /app/browser. Overview should show at least one session and pageview. Empty charts mean no beacons yet — never estimated traffic.
Install
The public key is safe in the browser. Do not put workspace API keys (mydle_…) in client code.
Script tag (any site)
Works on static HTML, WordPress, Shopify themes, and most CMS footers.
<script
src="https://YOUR_MYDLE_ORIGIN/sdk/mydle-browser.js"
data-key="pk_YOUR_PUBLIC_KEY"
data-endpoint="https://YOUR_MYDLE_ORIGIN/api/rum/v1/ingest"
data-release="1.0.0"
defer
></script>Next.js (App Router)
Load once from the root layout. The script must run in the browser only.
// app/layout.tsx
import Script from "next/script";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://YOUR_MYDLE_ORIGIN/sdk/mydle-browser.js"
strategy="afterInteractive"
data-key={process.env.NEXT_PUBLIC_MYDLE_BROWSER_KEY}
data-endpoint="https://YOUR_MYDLE_ORIGIN/api/rum/v1/ingest"
data-release={process.env.NEXT_PUBLIC_APP_VERSION ?? "dev"}
/>
</body>
</html>
);
}Set NEXT_PUBLIC_MYDLE_BROWSER_KEY=pk_… in .env.local.
React (Vite / CRA)
// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const script = document.createElement("script");
script.src = "https://YOUR_MYDLE_ORIGIN/sdk/mydle-browser.js";
script.defer = true;
script.dataset.key = import.meta.env.VITE_MYDLE_BROWSER_KEY;
script.dataset.endpoint =
"https://YOUR_MYDLE_ORIGIN/api/rum/v1/ingest";
script.dataset.release = import.meta.env.VITE_APP_VERSION ?? "dev";
document.body.appendChild(script);
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);Vue / Nuxt
Prefer a client-only plugin so SSR never touches window.
// plugins/mydle.client.ts (Nuxt)
export default defineNuxtPlugin(() => {
if (import.meta.server) return;
const script = document.createElement("script");
script.src = "https://YOUR_MYDLE_ORIGIN/sdk/mydle-browser.js";
script.defer = true;
script.dataset.key = useRuntimeConfig().public.mydleBrowserKey;
script.dataset.endpoint =
"https://YOUR_MYDLE_ORIGIN/api/rum/v1/ingest";
document.body.appendChild(script);
});Module import (bundlers)
When you ship from this repository (or copy sdk/mydle-browser.ts), call initMydleBrowser from a client entry:
import { initMydleBrowser } from "@/sdk/mydle-browser";
initMydleBrowser({
publicKey: "pk_YOUR_PUBLIC_KEY",
endpoint: "https://YOUR_MYDLE_ORIGIN/api/rum/v1/ingest",
release: "1.2.3",
sampleRate: 1,
network: true,
clicks: true,
});Configuration
Script attributes map to the module options below. Defaults are safe for production.
| Option | Script attr | Default | Description |
|---|---|---|---|
publicKey | data-key | required | Browser app public key from /app/browser |
endpoint | data-endpoint | /api/rum/v1/ingest | Absolute ingest URL on your Mydle deployment |
release | data-release | optional | Version string for release health (e.g. 1.2.3 or a git SHA) |
sampleRate | — | 1 | Fraction of sessions to capture (0–1). Module option only. |
network | — | true | Wrap fetch / XMLHttpRequest. Module option only. |
clicks | — | true | Capture click labels (tag / id / aria — never form values). Module option only. |
What is collected
Beacons go to POST /api/rum/v1/ingest (CORS + sendBeacon). Auth is the app publicKey, not a workspace API key.
| Type | When |
|---|---|
session | SDK init |
pageview | Full page load |
route_change | history.pushState / popstate (SPA) |
visibility | document.visibilitychange |
unload | pagehide |
vital | LCP, CLS, INP/FID, FCP, TTFB via PerformanceObserver |
error | window.error / unhandledrejection |
network | Wrapped fetch / XHR (URLs scrubbed) |
event | Timeline atoms (e.g. click labels) |
Never collected: session video / DOM replay, request bodies, cookies, passwords, or form field values.
Dashboard
Open /app/browser. Select an app, then use the section tabs. Deep-link with ?app=…&tab=… (for example ?tab=errors).
- Overview — KPIs + install snippet for the selected app
- Performance — Core Web Vitals and page latency
- Sessions — anonymous timelines (not video)
- JavaScript Errors — grouped fingerprints
- Network — recent, slow, failed requests
- Page Analytics — route aggregates
- AI Summary — evidence labels only
Export CSV / JSON / PDF from Overview when you need an offline snapshot.
Releases & source maps
Tag beacons with data-release / release, and record the same version in the product (Overview → Record release) or via API:
POST /api/v1/browser/apps/{appId}/releases
Authorization: Bearer mydle_…
Content-Type: application/json
{ "version": "1.2.3", "environment": "production" }Optional source maps (best-effort stack annotation on error ingest):
POST /api/v1/browser/apps/{appId}/sourcemaps
Authorization: Bearer mydle_…
{
"version": "1.2.3",
"fileUrl": "https://cdn.example.com/app.js",
"content": "{…source map JSON…}"
}Max map payload 512KB. Workspace API keys require Enterprise api_access. See API.
Privacy
- Anonymous ids in
localStorage(mydle_rum_aid) — not account identities - Sensitive query keys / values stripped from URLs client- and server-side
- Stacks and messages redact Bearer tokens and
password=/token=fragments - IP masking available on ingest (default when enabled)
Plans
Requires Pro+ (browser_monitoring). Free workspaces cannot enable RUM. Programmatic management via /api/v1/browser/… needs Enterprise api_access.
Troubleshooting
Dashboard stays empty
- Confirm the script loads (200 on
/sdk/mydle-browser.js) - Confirm ingest returns success for
POST /api/rum/v1/ingest - Check
data-keymatches the app'spk_…(typos fail silently on the client) - If using
sampleRate < 1, reload until a session is sampled
CORS or blocked beacon
Use the absolute ingest URL on your Mydle origin. Mixed content (HTTPS site → HTTP ingest) is blocked by browsers.
No SPA route changes
Route changes rely on history.pushState / popstate. Full page navigations emit pageview instead.
Missing network rows
Network capture is on by default for the module API. Script-tag installs use the built SDK with network + clicks enabled. Cross- origin failures still appear when status is available.
Limitations
- No session replay — timelines are structured events, not rrweb / DOM video
- Charts stay empty until real browsers send beacons — Mydle never estimates traffic
- Source-map annotation is best-effort; oversized or mismatched maps are skipped
- There is no multi-language npm org yet — use the hosted script or the first-party TypeScript module in this repo
Next steps
- SDKs overview — logs and tracing clients
- API — OpenAPI + Bearer automation
- AI Ops — how Observed / Likely / Unknown labels work
- Deployments — correlate releases with browser regressions
- Open Browser Monitoring