Vize

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.

Open the interactive example

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
Report Core Web Vitals: initial state
Initial state
Report Core Web Vitals: state after the verified interactions
After the verified interactions

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.