Skip to main content
7 min read

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.

MetricWhat it measuresGoodNeeds improvementPoor
LCP — Largest Contentful PaintWhen the main content becomes visible≤ 2.5 s≤ 4.0 s> 4.0 s
CLS — Cumulative Layout ShiftHow much visible content moves unexpectedly≤ 0.1≤ 0.25> 0.25
INP — Interaction to Next PaintWorst delay between a click/tap and the next paint≤ 200 ms≤ 500 ms> 500 ms
FCP — First Contentful PaintWhen the first text/image renders≤ 1.8 s≤ 3.0 s> 3.0 s
TTFB — Time to First ByteWhen 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:

  1. As soon as the tracker initializes, it registers observers for largest-contentful-paint, layout-shift, event (for INP), and paint (for FCP).
  2. TTFB is read once from performance.getEntriesByType('navigation')[0].responseStart.
  3. The final values are bundled into a single payload and sent to POST /api/track/<your-tracking-code>/performance when the visitor leaves the page (visibilitychange → hidden, plus pagehide for mobile bfcache).
  4. 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:

PlanPerformance time range
FreeLast 7 days
ProLast 30 days
ScaleLast 90 days
EnterpriseUp 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:

  1. Sample population. Zenovay measures every real visitor who loads your site. CrUX measures Chrome users who have opted into URL anonymization — a strict subset.
  2. 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"):

  1. Verify the tracker is firing. Open the Install Health diagnostic. If pageviews are not flowing, vitals won't either.
  2. Check the feature flag. Make sure feature_flags.enable_core_web_vitals is not explicitly set to false in the site's settings.
  3. 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.
  4. 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.
  5. Open browser DevTools → Network. With Performance filter, look for a POST to /api/track/<code>/performance. The response is a small JSON with success: 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.


Was this page helpful?