Measuring Core Web Vitals with your real visitors
There are plenty of plugins that promise to improve your Core Web Vitals. This one measures them, with the people who actually visit your site, and shows what they got. Every figure on the screen is the 75th percentile over the chosen period, separately for mobile and desktop, because that is how Google defines the thresholds. Nothing is estimated, nothing is simulated in a laboratory, and no advice is attached to a number the measurement cannot support.
Not the average, but the visitor right at the edge of the worst quarter
Google judges Core Web Vitals on the 75th percentile, not on the average. That is not a detail. An average hides the tail, and the tail is exactly where people give up. Out of every hundred measurements, you sort them in order, go to the seventy-fifth, and that figure is your score. Three in four visitors had it better, one in four worse.
Why your own figures do not match those from PageSpeed Insights
This is the question that always comes up after a day of measuring, and the answer is not that one of the two is wrong.
The lab score in PageSpeed Insights is a simulated page load on a simulated device, over a simulated connection
Useful for comparison, but it is not what your visitors actually get.
The field data in PageSpeed Insights comes from the Chrome User Experience Report
That report covers Chrome users who have given permission for it, and it needs enough traffic before it publishes anything at all. On a quiet site it stays empty, leaving you empty-handed even though your site does have visitors.
This plugin measures every browser that supports the underlying feature, even on that quiet site
The two will therefore differ, and that is stated on the screen instead of in a footnote.
What Google does with those figures, and why one slow template page can turn a whole group red in your report even though the assessment is per page, is explained in Core Web Vitals: Google assesses your website per page. That page explains the rule, this plugin gives you the measurement.
What is measured, and how
Four metrics, with the thresholds Google publishes.
LCP
, Largest Contentful Paint. Good up to and including 2.5 seconds, poor above 4 seconds.INP
, Interaction to Next Paint. Good up to and including 200 milliseconds, poor above 500.CLS
, Cumulative Layout Shift. Good up to and including 0.1, poor above 0.25.TTFB
, Time To First Byte. Not a Core Web Vital, but it sits underneath LCP. Good up to and including 800 milliseconds, poor above 1800.The measuring is our own implementation of the definitions Google publishes, written out in readable JavaScript rather than pulled in as a library. This was a deliberate choice: you can open the file and simply read what happens. It runs on the browser features that produce these figures, PerformanceObserver for the largest paint, the layout shifts and the event timings, and navigation timing for the first byte.
INP, following the same rule
Events that belong to one interaction are grouped by their interactionId, and the longest event in an interaction is that interaction's delay. Below fifty interactions, the worst one is reported; above that, one interaction in every fifty is allowed to be worse than the reported figure. That is the rule that stops a single outlier from defining an entire page.
Below twenty measurements you get no figure, but a count
A percentile over four page views is not a percentile. It looks exactly as precise as a percentile over four thousand, and it means nothing. That is how this kind of screen sends people in exactly the wrong direction, and that is why this plugin refuses to print the figure in that case.
How to install it, in four steps
No account is needed, no key and no registration. An ordinary WordPress site where you are an administrator is enough.
manage_options, so administrator.Click the download button above. You get theseo-webvitals-1.0.0.zip, 51 KB. Do not unzip it: WordPress wants the zip file itself.
In your WordPress: Plugins, Add New Plugin, Upload Plugin. Choose the file and click Install Now.
Click Activate Plugin. A new item appears in your admin menu. Your site does not change yet at this point.
Open the settings screen and go through the statuses. Everything that touches your public site stays off until you switch it on yourself.
What the plugin is not
This is where this plugin sets itself apart from the rest of the category, because the rest of the category promises speed.
- It is not a caching plugin and it does not make anything faster. Nothing is optimised, nothing is merged and nothing is deferred.
- It does not say what causes a slow figure. That is diagnosis, and diagnosis guessed from four numbers is gambling with a chart attached.
- It never suggests installing anything. There is no recommendation in it that happens to lead to a paid product.
- Its figures are not the figures Google uses for the search results. That difference is explained above, and it is also stated on the screen itself.
What it does give you: you know which page is slow on which device, how many measurements that rests on, and whether or not it comes down to your server
That is all you need to decide where to start, and that is exactly where most speed conversations get stuck.
Five fields in, and six things deliberately left out
Two rows from the same person cannot be recognised as belonging together. That is the design, not an accident. There is no session number, so nobody can be tracked, and in return there is also nothing to leak.
- The address of the page, without a query string and without an anchor
- Which metric it was
- The value
- The device class, mobile or desktop, derived from the width of the window
- The hour in which it happened
- The IP address, not readable and not as a fingerprint either
- A session, a visitor number or a user number
- A cookie or anything in localStorage or sessionStorage
- The user agent
- The query string
- Anything that leaves your server
Privacy: what is and is not recorded
This is the plugin in this series that comes closest to a visitor, because it measures inside that visitor's browser. That is also why this is the section most worth checking.
No IP address in the table
The address appears exactly once, in the rate limit, where it becomes a value through a key hash that expires within a minute. It never reaches the measurements table.No session and no visitor number
Two rows from the same person cannot be linked to each other. So there is nothing to track, and that is also why there is nothing to leak.No cookie and no browser storage
Search the unpacked zip forsetcookie, localStorage and sessionStorage. The only hits are in comments and in the readme.No user agent
Mobile or desktop is derived from the width of the window.No outgoing request
The script talks to your ownadmin-ajax.php and nothing else. There is no external address anywhere in the plugin.Do Not Track and Global Privacy Control are on
If the browser sends one, the script stops before it measures anything. You can switch this off, and it is on by default.Retention period
A daily task deletes measurements older than the configured period, ninety days by default. Deleting the plugin drops the table, the settings and the task.Why there is no nonce on the collection endpoint is explained in the frequently asked questions below, with the reason and the four measures that stand in its place.
Frequently asked questions
Why is there no nonce on the collection endpoint? Because a nonce quietly breaks the measurement without adding anything. A nonce is printed into the page, so on a site with full-page caching every visitor gets the same nonce and it stops working within a day. On a public endpoint that anonymous visitors need to be able to reach, it is not security either. What is in place instead: the master switch, a same-origin check, a rate limit per address, and validation that only accepts the four known metrics, only figures within their published range, and only an address from this site. Every admin action does use a nonce plus a permissions check.
Does it slow down my site?
The script is a single file of a few kilobytes, loaded in the footer, with no dependencies, and it does not block rendering. It sends one request per page view, at the moment the page is left, via sendBeacon.
Does it work with a caching plugin?
Yes, and that is part of the point. Nothing in the markup expires, so a page served from cache keeps measuring, and what gets measured then is what your visitors actually get from that cache.
Why does one metric have fewer measurements than another?
Because not every browser has every measuring feature. A browser that cannot measure something contributes nothing for it, and that gap is not filled with a zero.
Why does a page say too few measurements?
Because it has fewer measurements than the threshold on the settings screen, twenty by default. Set it to 1 temporarily if you want to see the first figures, then set it back.
Does it work on multisite?
Yes. Every site in the network gets its own table and its own settings. Deleting the plugin cleans up every site.
What it gives you in the first week
The practical route: switch it on, let it run for a week, and only then take a look. Before that you will have too few measurements, and the plugin says so too. If you then see that a page is slow but do not know why visitors get stuck on it, the heatmap and scrollmap shows what they do on that same page. If your site runs on WordPress and you also want to check it for accessibility, the accessibility plugin does that on the points that can be tested automatically. If you would like to talk it through, book a call.