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.
Install
Section titled “Install”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.
What gets measured
Section titled “What gets measured”| Metric | Source |
|---|---|
| LCP | Largest Contentful Paint — 'largest-contentful-paint' PerformanceObserver. |
| FCP | First Contentful Paint — 'paint' observer, name === 'first-contentful-paint'. |
| CLS | Cumulative Layout Shift — sum of 'layout-shift' entries excluding user input. |
| INP | Interaction to Next Paint — 'event' observer, max interaction duration. |
| TTFB | Time To First Byte — responseStart - startTime from the navigation entry. |
SPA navigation
Section titled “SPA navigation”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.
Browser support
Section titled “Browser support”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.
Wire shape
Section titled “Wire shape”{ "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.