use-web-vitals
Standard [good, poor] thresholds: values up to the first bound are "good", values above the second are "poor".
| Package | @vizejs/composable/use-web-vitals |
| Own the source | vize lib pull composable:use-web-vitals |
| Runtime exports | WEB_VITALS_THRESHOLDS, rateMetric, useWebVitals |
| Gzip budget | 4608 B |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/composable
Import the entry directly. Call lifecycle-bound utilities inside <script setup> so cleanup follows the component scope; consult the runtime contract for this entry's SSR and hydration behavior.
import { WEB_VITALS_THRESHOLDS, rateMetric, useWebVitals } from "@vizejs/composable/use-web-vitals";
Try it
Report Core Web Vitals
Measure LCP, CLS, INP, FCP and TTFB from real visits and send them to analytics.
What to observe: Metrics appear as the page reports them; first contentful paint usually comes first.
Values arrive over time and some are only final when the page is hidden. Use onMetric to send each update.
Compare the initial and interacted states


Copy the example
Save this complete SFC in your Vue project. Its public imports and source are the same ones executed in the preview.
<script setup lang="ts">
import { computed } from "vue";
import { useMounted } from "@vizejs/composable/use-mounted";
import { useWebVitals } from "@vizejs/composable/use-web-vitals";
const { metrics, supported } = useWebVitals({ reportAllChanges: true });
const mounted = useMounted();
const rows = computed(() =>
Object.values(metrics.value).map(
(metric) => `${metric?.name} ${Math.round(metric?.value ?? 0)} (${metric?.rating})`,
),
);
</script>
<template>
<div class="composable-example">
<p>Report Core Web Vitals from real visits to your analytics.</p>
<ul aria-label="Web vitals">
<li v-for="row in rows" :key="row">{{ row }}</li>
</ul>
<output aria-live="polite">{{
!mounted
? "Waiting for the first metric…"
: !supported
? "This browser does not report Web Vitals."
: rows.length === 0
? "Waiting for the first metric…"
: `${rows.length} metrics reported`
}}</output>
</div>
</template>
Runtime contract
| Utility | Category | Stability | SSR | Hydration | Cleanup | Targets | Host globals | Uses |
|---|---|---|---|---|---|---|---|---|
useWebVitals |
performance | experimental | deterministic-fallback | caller-managed | caller, reactive-scope | web, desktop | window |
tryOnScopeDispose, usePerformanceObserver |
rateMetric |
performance | experimental | safe | stable | none | web, server, worker, native, desktop, terminal | — | — |
API
WEB_VITALS_THRESHOLDS
Standard [good, poor] thresholds: values up to the first bound are "good", values above the second are "poor". Times are milliseconds; CLS is unitless.
const WEB_VITALS_THRESHOLDS
rateMetric
Rate a metric value with WEB_VITALS_THRESHOLDS.
function rateMetric(name: WebVitalName, value: number): WebVitalRating
useWebVitals
Measure LCP, CLS, INP, FCP and TTFB locally, without dependencies. - LCP: last candidate before the first input or hidden page. - CLS: largest session window (shifts < 1 s apart, window ≤ 5 s), ignoring shifts after recent input. - INP: per-interaction maximum duration; the highest after ignoring one per 50 interactions (a p98 approximation over the 10 longest). The interaction count only includes interactions above durationThreshold. - FCP: first-contentful-paint before the page was first hidden. - TTFB: navigation responseStart - activationStart, clamped at 0. Values are not adjusted for prerender activation except TTFB. Observers and listeners are released when the owning reactive scope stops; outside a scope the caller owns stop(). Server rendering: nothing is observed, metrics is {} and supported is false.
function useWebVitals(options: UseWebVitalsOptions = {}): WebVitalsControls
const { metrics, onMetric } = useWebVitals();
onMetric((metric) => navigator.sendBeacon("/vitals", JSON.stringify(metric)));
Types
WebVitalMetric
Snapshot of one metric.
| Member | Type | Description |
|---|---|---|
name |
WebVitalName |
Metric name. |
value |
number |
Current value (milliseconds, or the unitless CLS score). |
rating |
WebVitalRating |
Rating of value. |
delta |
number |
Change since the value last reported through onMetric (the full value before that). |
entries |
number |
Number of performance entries that make up value. |
id? |
string |
Identifier stable for this metric during the page load. |
WebVitalsDocumentHost
Minimal document consumed by useWebVitals.
| Member | Type | Description |
|---|---|---|
visibilityState |
string |
Current visibility ("visible" or "hidden"). |
addEventListener |
(type: WebVitalsDocumentEvent, listener: (event: { readonly timeStamp: number }) => void, options?: { readonly capture?: boolean }) => void |
Subscribe to a document event. |
removeEventListener |
(type: WebVitalsDocumentEvent, listener: (event: { readonly timeStamp: number }) => void, options?: { readonly capture?: boolean }) => void |
Unsubscribe from a document event. |
UseWebVitalsOptions
Options for useWebVitals.
| Member | Type | Description |
|---|---|---|
PerformanceObserver? |
MaybeRef<PerformanceObserverHost | null | undefined> |
PerformanceObserver constructor for alternate runtimes and tests. |
document? |
MaybeRefOrGetter<WebVitalsDocumentHost | null | undefined> |
Document whose visibility finalizes metrics and whose first input stops LCP. |
reportAllChanges? |
boolean |
Report every change through onMetric instead of only final values. |
durationThreshold? |
number |
Minimum Event Timing duration considered for INP, in milliseconds. |
WebVitalsControls
Reactive state and actions returned by useWebVitals.
| Member | Type | Description |
|---|---|---|
supported |
ComputedRef<boolean> |
Whether at least one metric can be observed. |
metrics |
Readonly<ShallowRef<WebVitalsRecord>> |
Metrics measured so far, replaced on each change. |
onMetric |
(listener: (metric: WebVitalMetric) => void) => () => void |
Subscribe to reported metrics. FCP and TTFB are reported once measured; LCP once finalized (first input or hidden page); CLS and INP whenever the page becomes hidden with a changed value. With reportAllChanges, every change is reported. |
stop |
() => void |
Stop every observer and listener. Repeated calls are safe. |