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

MetricWhat it measuresGoodNeeds improvementPoor
LCPLargest Contentful Paint≤ 2.5 s≤ 4 s> 4 s
INPInteraction to Next Paint≤ 200 ms≤ 500 ms> 500 ms
CLSCumulative Layout Shift≤ 0.1≤ 0.25> 0.25
FCPFirst Contentful Paint≤ 1.8 s≤ 3 s> 3 s
TTFBTime to First Byte≤ 800 ms≤ 1.8 s> 1.8 s
tbt, longtaskTotal 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

typescript
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.

typescript
gl.captureVital("lcp", 2300, {
  route: "/checkout",
  rating: "good",                // optional — overrides local classification
  attributes: { tenant: "acme" },
})
python
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:

  1. Five big tiles (LCP / INP / CLS / FCP / TTFB) showing p75 + sample count, coloured by the table above.
  2. Rating distribution bars per LCP / INP / CLS — % good / needs-improvement / poor.
  3. 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

gql
# 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