Web Vitals (RUM)
Real-User Monitoring of Core Web Vitals. The browser SDK collects vitals automatically with useEnvelope: true. They're persisted in the web_vitals_raw hypertable and shown on the dedicated Web Vitals dashboard.
Metrics
| Metric | What it measures | Good | Needs improvement | Poor |
|---|---|---|---|---|
| LCP | Largest Contentful Paint | ≤ 2.5 s | ≤ 4 s | > 4 s |
| INP | Interaction to Next Paint | ≤ 200 ms | ≤ 500 ms | > 500 ms |
| CLS | Cumulative Layout Shift | ≤ 0.1 | ≤ 0.25 | > 0.25 |
| FCP | First Contentful Paint | ≤ 1.8 s | ≤ 3 s | > 3 s |
| TTFB | Time to First Byte | ≤ 800 ms | ≤ 1.8 s | > 1.8 s |
tbt, longtask | Total Blocking Time, long tasks | — | — | — |
The SDK classifies each sample as good / needs-improvement / poor locally before sending, so dashboards can render distribution bars without server-side aggregation per request.
Automatic capture
import { GoodLogs } from "@aj-2000-test/goodlogs-sdk"
const gl = new GoodLogs({ apiKey: "gl_pk_...", useEnvelope: true })
// PerformanceObserver wires LCP / INP / CLS / FCP / TTFB automatically.
Disable with autocapture: false if you want full control.
Manual capture
Useful for SSR / synthetic / longtask observers.
gl.captureVital("lcp", 2300, {
route: "/checkout",
rating: "good", // optional — overrides local classification
attributes: { tenant: "acme" },
})
gl.capture_vital("lcp", 2300, route="/checkout")
Invalid metrics (anything outside lcp|inp|cls|fcp|ttfb|tbt|longtask) and NaN / negative values are silently dropped on both client and server.
What the dashboard shows
/dashboard/web-vitals:
- Five big tiles (LCP / INP / CLS / FCP / TTFB) showing p75 + sample count, coloured by the table above.
- Rating distribution bars per LCP / INP / CLS —
% good / needs-improvement / poor. - Slowest pages by p75 per metric — top 20 routes each.
/dashboard/performance also has a 5-tile row at the top for a unified view alongside server-side latency.
Querying
# Overall p75 last 24h
from:web_vitals | p75(value_ms), count by metric | last:24h
# Slowest LCP routes
from:web_vitals metric:lcp | p75(value_ms), count by route | last:24h | top 20
# Distribution buckets for INP
from:web_vitals metric:inp | count by rating | last:24h
# Poor-bucket users
from:web_vitals metric:lcp rating:poor | count_distinct(user_id) | last:7d
# Attribute-level filtering (anything you passed via attributes)
from:web_vitals attributes.tenant:acme metric:lcp | p75(value_ms) | last:24h