Core Web Vitals
Zenovay collects Google's Core Web Vitals (LCP, CLS, INP) plus two supporting metrics (FCP, TTFB) from every real visitor session. Data appears in the Performance tab of each domain dashboard.
Nothing to install. Web Vitals collection is part of the standard Zenovay tracker. If your tracking script is already running, vitals start flowing as soon as visitors load your pages.
What Web Vitals measure
Core Web Vitals are Google's stable signals for real-world page experience. They appear in Google Search Console, factor into ranking, and correlate strongly with bounce rate and conversion.
| Metric | What it measures | Good | Needs improvement | Poor |
|---|---|---|---|---|
| LCP — Largest Contentful Paint | When the main content becomes visible | ≤ 2.5 s | ≤ 4.0 s | > 4.0 s |
| CLS — Cumulative Layout Shift | How much visible content moves unexpectedly | ≤ 0.1 | ≤ 0.25 | > 0.25 |
| INP — Interaction to Next Paint | Worst delay between a click/tap and the next paint | ≤ 200 ms | ≤ 500 ms | > 500 ms |
| FCP — First Contentful Paint | When the first text/image renders | ≤ 1.8 s | ≤ 3.0 s | > 3.0 s |
| TTFB — Time to First Byte | When the server's first byte arrives | ≤ 800 ms | ≤ 1.8 s | > 1.8 s |
Thresholds are Google's published recommendations and are reflected directly in the Zenovay dashboard color coding (green / amber / red).
How Zenovay collects the data
Collection happens entirely in the browser using the standard PerformanceObserver API and the Navigation Timing entry — no third-party library, no extra script.
A short flow:
- As soon as the tracker initializes, it registers observers for
largest-contentful-paint,layout-shift,event(for INP), andpaint(for FCP). - TTFB is read once from
performance.getEntriesByType('navigation')[0].responseStart. - The final values are bundled into a single payload and sent to
POST /api/track/<your-tracking-code>/performancewhen the visitor leaves the page (visibilitychange→ hidden, pluspagehidefor mobile bfcache). - The API tags each row with
country_code,device_type,browser, and (when available)connection_type.
The payload carries no field values from forms, no DOM contents, and no PII beyond what is already part of an analytics page view (the URL).
One beacon per page lifecycle. Vitals are sent exactly once when the visitor leaves a page. Single-page apps that route on the client send a fresh beacon per page transition, just like pageview events.
Where to view your data
Open any domain in app.zenovay.com and click the Performance tab.
You'll see:
- A metric sidebar with the current value, threshold rating, and percentile distribution for each of LCP / CLS / INP / FCP / TTFB.
- A detail panel with the selected metric's trend over your chosen time range, plus a contextual explainer of what affects it.
- Routes and Countries breakdowns showing which pages and which regions perform worst.
- Controls for:
- Device — All / Desktop / Mobile
- Percentile — P75 / P90 / P95 / P99 (Google reports P75 by default)
- Time range — capped per plan (see below)
Percentile guidance
Google reports the 75th percentile of real users for Search Console ranking signals. Higher percentiles (P90, P95, P99) highlight your slowest visitors — useful when investigating a specific regression or a tail of slow devices.
Plan availability and retention
Web Vitals collection itself is included on every plan, including Free. Time-range access is plan-tiered:
| Plan | Performance time range |
|---|---|
| Free | Last 7 days |
| Pro | Last 30 days |
| Scale | Last 90 days |
| Enterprise | Up to 1 year |
These caps match the broader analytics retention tiers — see Pricing & Plan Limits for the underlying rules.
Disabling Web Vitals for a specific site
Vitals are on by default. To opt out for a single tracked site, set the feature flag in that site's settings:
{
"feature_flags": {
"enable_core_web_vitals": false
}
}
When the flag is false:
- The tracker skips registering the observers entirely (zero browser-side cost).
- The API endpoint accepts the beacon but does not store anything (silent no-op for in-flight requests).
To re-enable, set the flag back to true or remove it entirely (default-on).
Frequently asked questions
Why is INP missing on Safari?
Safari did not implement the PerformanceObserver event entry type with interactionId until very recently. On older Safari versions Zenovay reports LCP, CLS, FCP, and TTFB and leaves INP as null for that session. The dashboard will simply show fewer INP samples until Safari coverage catches up — no action required from you.
Do single-page apps get a beacon per route change?
Yes. The tracker resets the observers on each tracked page transition (including SPA route changes that fire pushState or replaceState). You will see one performance row per page view, not one per browser session.
Will vitals collection affect my site's performance?
PerformanceObserver is a passive browser API designed for exactly this use case. The observers fire on existing browser events; there is no polling and no extra work on the main thread. The single beacon at page-unload is sent via fetch with keepalive: true so it does not block navigation.
Why don't my numbers match Google's CrUX data?
Two normal reasons:
- Sample population. Zenovay measures every real visitor who loads your site. CrUX measures Chrome users who have opted into URL anonymization — a strict subset.
- Time window. CrUX is a rolling 28-day window of Chrome-only data. Zenovay lets you pick your own window and slice by device, percentile, route, and country.
Both signals are valid; they answer slightly different questions. CrUX is what Google uses for ranking; Zenovay is what you use for debugging.
Where can I find the threshold values in code?
They live in app-zenovay/lib/api/performance.ts (frontend constants) and mirror Google's published thresholds. Changing them requires a code update — they are deliberately not customer-configurable so that "good" / "needs improvement" / "poor" labels stay comparable across the product.
Troubleshooting empty dashboards
If the Performance tab shows the empty state ("No performance data yet"):
- Verify the tracker is firing. Open the Install Health diagnostic. If pageviews are not flowing, vitals won't either.
- Check the feature flag. Make sure
feature_flags.enable_core_web_vitalsis not explicitly set tofalsein the site's settings. - Give it some traffic. Vitals beacons fire on page-unload. A site with very low traffic, or one tested only by you with a freshly-cleared cache, may need a few sessions before numbers appear.
- Open the dashboard with a wider time range. A new site on the Free plan defaults to the last 7 days — if you just installed, widen to the maximum your plan allows.
- Open browser DevTools → Network. With
Performancefilter, look for aPOSTto/api/track/<code>/performance. The response is a small JSON withsuccess: true.
If the request is firing but no data appears, contact [email protected] with your tracking code — there is an explicit per-website opt-out flag and we can confirm it is unset.
Related
- Install Health — confirm the tracker is firing
- Real-time Tracking — see visitors as they land
- Privacy Compliance — what data the tracker sends and why