Skip to content

Web Vitals

The vitals subpath wires up PerformanceObserver for the five Core Web Vitals and emits one vitals event per pageview, fired on pagehide or visibilitychange → 'hidden'. Bundle target: under 1 kB gzip.

import { installVitals } from '@syntarie/tracking/vitals';
import { send } from '@syntarie/tracking'; // internal helper exposed for module composition
const teardown = installVitals(send);
// On hot-reload or when tearing down the host page:
teardown();

installVitals returns a function that disconnects every observer. Call it in your bundler’s HMR hook to avoid duplicated observers.

MetricSource
LCPLargest Contentful Paint — 'largest-contentful-paint' PerformanceObserver.
FCPFirst Contentful Paint — 'paint' observer, name === 'first-contentful-paint'.
CLSCumulative Layout Shift — sum of 'layout-shift' entries excluding user input.
INPInteraction to Next Paint — 'event' observer, max interaction duration.
TTFBTime To First Byte — responseStart - startTime from the navigation entry.

By default, the vitals event fires on pagehide — once per real page load. For client-side route changes you want a fresh per-route measurement:

import { endVitalsForPageview } from '@syntarie/tracking/vitals';
// Inside your router's beforeEach hook:
router.beforeEach(() => {
endVitalsForPageview();
});

endVitalsForPageview flushes the current accumulator and resets it for the next route. The next user-visible paint after the route change becomes the new LCP / FCP for that route.

The module is a no-op on browsers without PerformanceObserver. It does not throw and does not log a warning — older browsers simply do not contribute vitals data.

{
"type": "vitals",
"url": "https://example.com/pricing",
"ts": 1714665600000,
"props": {
"lcp_ms": 1432,
"fcp_ms": 612,
"cls": 0.04,
"inp_ms": 184,
"ttfb_ms": 121
}
}

Missing metrics (e.g. INP on a navigation with no interaction) are simply omitted from props. The query API returns counts and per-day aggregates; percentile queries arrive in v1.1 alongside the analysis-models layer.