astro-webvitalsv0.4.11

CASOON Open Source

Web Vitals from real visits, for Astro.

One component in your layout measures LCP, CLS, INP, FCP and TTFB on every visit and sends them to an endpoint you own: no third-party script, no account, and nothing collected without consent if you say so.

pnpm add @casoon/astro-webvitals
npm install @casoon/astro-webvitals
MITnpm 0.4.11Astro 4–7your endpoint only
Screenshot of the Web Vitals dashboard of the live demo after a sitemap pass: 4 pages, 20 latest metrics, 0 page issues. The card for /de/contact/ shows CLS 0.000, DOM 51 ms, FCP 112 ms, LCP 112 ms and TTFB 54 ms, all rated Good, and 10 passed page checks such as title length, canonical, language, one H1, Open Graph, skip link, image alt, link text and form labels.
The local dashboard of the live demo after a sitemap pass in one browser, 26 October 2026: QA data from a single visit, not field data.

Who it is for

Web Vitals from Google’s web-vitals
5
runtime dependency
1
supported Astro major versions
4–7
gzip entry script in the demo build
4.7 kB

From a page load to your endpoint

Consent, Do Not Track and the sample rate are checked in the browser on every page load. If one of them says no, no observer is registered and nothing is measured.

Otherwise the metrics come from Google’s web-vitals library, are collected in a batch and sent when the page is hidden, with sendBeacon where the browser allows it.

From a page load to your endpointOn every page load the component first checks consent, Do Not Track and the sample rate in the browser; if a gate closes, nothing is measured. Otherwise Google's web-vitals library measures LCP, CLS, INP, FCP and TTFB. Every metric is dispatched as a webvitals:metric browser event and, when an endpoint is set, collected in a batch that is flushed when the batch is full, after the batch interval or when the page is hidden, and sent with sendBeacon, or with fetch and keepalive when custom headers are set.From a page load to your endpointPage load → Gates open?Gates open? → Nothing measured: closedGates open? → web-vitals: openweb-vitals → webvitals:metric: dispatchesweb-vitals → Batch: recordsBatch → Your endpoint: POSTclosedopendispatchesrecordsPOSTPage load – <WebVitals /> runsPage load<WebVitals /> runsGates open? – consent, DNT, sample rateGates open?consent, DNT, sample rateNothing measured – no observer registeredNothing measuredno observer registeredweb-vitals – LCP, CLS, INP, FCP, TTFBweb-vitalsLCP, CLS, INP, FCP, TTFBwebvitals:metric – browser eventwebvitals:metricbrowser eventBatch – sent when the page hidesBatchsent when the page hidesYour endpoint – sendBeacon or fetchYour endpointsendBeacon or fetch
From a page load to your endpointOn every page load the component first checks consent, Do Not Track and the sample rate in the browser; if a gate closes, nothing is measured. Otherwise Google's web-vitals library measures LCP, CLS, INP, FCP and TTFB. Every metric is dispatched as a webvitals:metric browser event and, when an endpoint is set, collected in a batch that is flushed when the batch is full, after the batch interval or when the page is hidden, and sent with sendBeacon, or with fetch and keepalive when custom headers are set.From a page load to your endpointPage load → Gates open?Gates open? → Nothing measured: closedGates open? → web-vitals: openweb-vitals → webvitals:metric: dispatchesweb-vitals → Batch: recordsBatch → Your endpoint: POSTclosedopendispatchesrecordsPOSTPage load – <WebVitals /> runsPage load<WebVitals /> runsGates open? – consent, DNT, sample rateGates open?consent, DNT, sample rateNothing measured – no observer registeredNothing measuredno observer registeredweb-vitals – LCP, CLS, INP, FCP, TTFBweb-vitalsLCP, CLS, INP, FCP, TTFBwebvitals:metric – browser eventwebvitals:metricbrowser eventBatch – sent when the page hidesBatchsent when the page hidesYour endpoint – sendBeacon or fetchYour endpointsendBeacon or fetch
From a page load to your endpointOn every page load the component first checks consent, Do Not Track and the sample rate in the browser; if a gate closes, nothing is measured. Otherwise Google's web-vitals library measures LCP, CLS, INP, FCP and TTFB. Every metric is dispatched as a webvitals:metric browser event and, when an endpoint is set, collected in a batch that is flushed when the batch is full, after the batch interval or when the page is hidden, and sent with sendBeacon, or with fetch and keepalive when custom headers are set.From a page load to your endpointPage load → Gates open?Gates open? → Nothing measured: closedGates open? → web-vitals: openweb-vitals → webvitals:metric: dispatchesweb-vitals → Batch: recordsBatch → Your endpoint: POSTclosedopendispatchesrecordsPOSTPage load – <WebVitals /> runsPage load<WebVitals />runsGates open? – consent, DNT, sample rateGates open?consent, DNT,sample rateNothing measured – no observer registeredNothingmeasuredno observerregisteredweb-vitals – LCP, CLS, INP, FCP, TTFBweb-vitalsLCP, CLS, INP,FCP, TTFBwebvitals:metric – browser eventwebvitals:metricbrowser eventBatch – sent when the page hidesBatchsent when thepage hidesYour endpoint – sendBeacon or fetchYour endpointsendBeacon orfetch
From a page load to your endpointOn every page load the component first checks consent, Do Not Track and the sample rate in the browser; if a gate closes, nothing is measured. Otherwise Google's web-vitals library measures LCP, CLS, INP, FCP and TTFB. Every metric is dispatched as a webvitals:metric browser event and, when an endpoint is set, collected in a batch that is flushed when the batch is full, after the batch interval or when the page is hidden, and sent with sendBeacon, or with fetch and keepalive when custom headers are set.From a page load to your endpointPage load → Gates open?Gates open? → Nothing measured: closedGates open? → web-vitals: openweb-vitals → webvitals:metric: dispatchesweb-vitals → Batch: recordsBatch → Your endpoint: POSTclosedopendispatchesrecordsPOSTPage load – <WebVitals /> runsPage load<WebVitals />runsGates open? – consent, DNT, sample rateGates open?consent, DNT,sample rateNothing measured – no observer registeredNothingmeasuredno observerregisteredweb-vitals – LCP, CLS, INP, FCP, TTFBweb-vitalsLCP, CLS, INP,FCP, TTFBwebvitals:metric – browser eventwebvitals:metricbrowser eventBatch – sent when the page hidesBatchsent when thepage hidesYour endpoint – sendBeacon or fetchYour endpointsendBeacon orfetch

While you build

debug opens an overlay on the page: the current Web Vitals, an SEO inspection, a small accessibility heuristic and the console. It is a development aid, not a WCAG audit, and import.meta.env.DEV keeps it out of production.

Dashboard and overlay
Screenshot of the debug overlay, tab Core Vitals: performance score 100 with 5 of 5 metrics measured, LCP 36 ms, CLS 0.000 and INP 24 ms, each rated good.Screenshot of the debug overlay, tab Accessibility, on a sample page with deliberate gaps: 4 issues found in 3 categories, missing alt text (1), missing labels (2) and heading issues (1).

Quickstart

Three steps from install to your first reports. The endpoint, the dashboard route and every option are covered in the documentation.

  1. Install @casoon/astro-webvitals with one of the lines above.
  2. Add <WebVitals /> once to your shared layout, right before </body>.
  3. Store the batches in a same-origin route, or listen for the webvitals:metric event.
src/layouts/Layout.astroLayout
---
import { WebVitals } from '@casoon/astro-webvitals';
---
<WebVitals endpoint="/api/analytics/vitals" sampleRate={0.1} />

New in v0.4.11

Changelog →

Frequently asked questions

Does this work with a strict Content Security Policy?

Yes. With Astro's security.csp, the component's configuration is passed as inert JSON (<script type="application/json">) and all code runs from the bundled module script Astro serves from your own origin, so no 'unsafe-inline' is needed.

What about the debug overlay and console dock — do those need CSP exceptions?

No. The debug overlay and console dock use no inline event handlers; clicks are dispatched from a data-webvitals-action attribute instead of onclick.

Where do the metrics go?

Every reported metric is emitted locally as a webvitals:metric browser event, which you can listen for without an HTTP endpoint. If you pass an endpoint prop, metrics are also batched and sent there with sendBeacon where the browser allows it.

What does the local dashboard show?

It's a static route backed only by the browser's localStorage — no server or database. It shows the latest metric per route, and Run sitemap pass opens every page from your sitemap.xml and adds browser-visible page checks: title, description, canonical, language, H1, Open Graph, skip link, image alt, link text, form labels and valid JSON-LD.

Which Astro versions and metrics are supported?

Astro 4–7. The component reports the Core Web Vitals LCP, CLS and INP, the supporting metrics FCP and TTFB, navigation timings, and optionally Long Tasks and diagnostic attribution.