CASOON Open Source
Web Vitals from real visits, for Astro.
astro-webvitals measures LCP, CLS, INP, FCP and TTFB with Google’s web-vitals library and sends them in batches to an endpoint you own. Collection can be gated on consent, Do Not Track and a sample rate; a local dashboard and a development overlay help while you build.
pnpm add @casoon/astro-webvitalsnpm install @casoon/astro-webvitalspass Title 26 characters issue Description Missing pass Canonical Present pass Language en pass H1 One heading issue Open Graph Title, description or image missing pass Skip link Present issue Image alt 1 image without alt issue Link text 1 empty link issue Form labels 1 unlabelled control info Structured data No invalid JSON-LD found
- 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
What it does
Official metric definitions
LCP, CLS, INP, FCP and TTFB come from the web-vitals package, with its ratings, stable metric IDs and deltas, so repeated CLS and INP reports aggregate correctly.
Reports go to your endpoint
Metrics are batched, flushed when the page is hidden and sent with sendBeacon where possible. Without an endpoint nothing is sent; every metric is also dispatched as a webvitals:metric event.
Collection gates
consent={false} and respectDnt stop the client before any observer is registered. sampleRate is decided per page load in the browser, not baked into a static build.
Local dashboard
webVitalsDashboard() adds a static, noindex route that reads this browser’s localStorage, runs a sitemap pass and shows page checks – for development and QA, not field analytics.
Development overlay
debug shows live metrics, an SEO inspection and a small accessibility heuristic. It is a development aid, not a WCAG audit.
Generated at build time
All examples →<script>(function(){const config = {
"debug": false,
"consoleDock": false,
"position": "bottom-right",
"desktopPositions": {
"top-right": "top: 16px; right: 16px;",
"top-left": "top: 16px; left: 16px;",
"bottom-right": "bottom: 16px; right: 16px;",
"bottom-left": "bottom: 16px; left: 16px;"
},
"mobilePosition": "bottom: 0; left: 0; right: 0;",
"batchReporting": true,
"batchInterval": 5000,
"checkAccessibility": false,
"highlightAccessibility": false,
"extendedMetrics": false,
"smartDetection": false,
"performanceBudget": {
"LCP": 2500,
"CLS": 0.1,
"FCP": 1800,
"TTFB": 800,
"INP": 200
},
"headers": {},
"sampleRate": 1,
"trackSoftNavigations": false,
"attribution": false,
"trackLongTasks": false,
"maxBatchSize": 10,
"retryFailedMetrics": false,
"respectDnt": false,
"consent": true,
"dashboard": false
};
window.__WEBVITALS_CONFIG__ = config;
})();</script>
<script type="module" src="/_astro/WebVitals.astro_astro_type_script_index_0_lang.KhS4lISl.js"></script>
What <WebVitals /> adds to a statically built page, captured from the demo build by examples/capture.mjs and formatted for reading.
examples/output/default.htmlWhy this site does not measure itself
Quickstart
Three steps from install to your first reports. The full walkthrough lives in the documentation.
- Install
@casoon/astro-webvitals. - Add
<WebVitals />once to your shared layout, right before</body>. - Store the batches in a same-origin route, or listen for the
webvitals:metricevent.
---
import { WebVitals } from '@casoon/astro-webvitals';
---
<html lang="en">
<body>
<slot />
<WebVitals endpoint="/api/analytics/vitals" sampleRate={0.1} />
</body>
</html>import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request }) => {
const { metrics } = await request.json();
for (const metric of metrics) {
// Validate, rate-limit and store; sum deltas per metric.id.
}
return new Response(null, { status: 204 });
};