web-vitals: measuring Core Web Vitals in real user sessions
Essential metrics for a healthy site.
At a glance
- What is it?
- The web-vitals library reports CLS, INP, LCP, FCP and TTFB from actual browsers, matching how Chrome measures them. It is a measurement tool, not an optimisation tool, and its value depends entirely on what you do with the numbers.
- Who is it for?
- Adopt web-vitals if you need field data that matches what Chrome reports to CrUX and PageSpeed Insights, and you already have somewhere to send the numbers. Do not adopt it if you only want a one-off lab score: Lighthouse or the PageSpeed Insights UI answers that question without shipping code to users.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What web-vitals measures, and who needs the numbers
The library reports the three Core Web Vitals (CLS, INP, LCP) plus FCP and TTFB, and the README describes it as measuring "all the Web Vitals metrics on real users, in a way that accurately matches how they're measured by Chrome and reported to other Google tools" such as the Chrome User Experience Report, PageSpeed Insights and Search Console's Speed Report. That sentence is the whole pitch. If you have ever seen a PageSpeed Insights field section that looks nothing like your own instrumentation, this library exists to close that gap.
The audience is front-end and performance engineers who own a production site and want per-user, per-page measurements rather than a synthetic score. It is not a diagnostic dashboard and it does not store anything. It hands you numbers through callbacks and expects you to ship them somewhere. Teams that want a finished report should look at the Chrome extension or the PageSpeed Insights UI instead; teams that want raw field data inside their own analytics pipeline are the ones this fits.
How the buffered PerformanceObserver approach changes your loading strategy
The mechanism is the browser's PerformanceObserver, and specifically the buffered flag. The README states that using buffered lets the library access performance entries that occurred before the library was loaded. That has a direct consequence for how you deploy it: you do not need to inline it in the head or block rendering to catch early paint events. The README goes further and says the library "should be deferred until after other user-impacting code has loaded."
That is an unusual instruction for a performance library, and it is the right one. Most measurement scripts are loaded early and become part of the problem they measure. Here the buffered entries are replayed to the observer when it subscribes, so late loading costs you nothing in completeness. The trade-off is that the library depends on PerformanceObserver support and on the buffered flag behaving correctly; the README has a Browser Support section, but the README does not enumerate which browsers are excluded, so treat that section as the place to check before you commit.
Data flow is simple: you register a callback per metric, the library fires it when the metric is final (or on every change, if you ask for that), and you forward the metric object to your endpoint. There is no built-in transport, no queue, no retry. Every delivery concern is yours.
Install web-vitals from npm and log your first LCP
The npm install is a single command. The README gives it as:
npm install web-vitalsAfter that, import the metric functions you want. The README's basic example imports three of them from the package root:
import {onLCP, onINP, onCLS} from 'web-vitals';Passing console.log as the callback is the fastest way to confirm the wiring works. The README's own CDN example does exactly that, calling onCLS(console.log), onINP(console.log) and onLCP(console.log). In a browser console you should see metric objects appear as each measurement finalises; LCP and CLS may take until the page is hidden to settle, so do not expect all three immediately on load.
If your project cannot use a build step, the README documents loading from a CDN with a module script, appending the ?module parameter:
<script type="module">
import {onCLS, onINP, onLCP} from 'https://unpkg.com/web-vitals@5?module';
onCLS(console.log);
onINP(console.log);
onLCP(console.log);
</script>The README is explicit that unpkg, jsDelivr and cdnjs are shown as examples only, are not affiliated with Google, and carry no guarantee of continued availability. It recommends self-hosting the built files for security, reliability and performance. Take that recommendation seriously: a third-party CDN in your critical measurement path is a dependency you did not choose deliberately.
The attribution build and the size trade-off it asks you to accept
Knowing your LCP is 4.1 seconds is not the same as knowing why. The attribution build adds diagnostic data to each metric object so you can identify the root cause, and the README frames it as the second step after you have scores that are not good. Switching is a one-line change to the import path:
import {onLCP, onINP, onCLS} from 'web-vitals/attribution';The function signatures are identical; the difference is that metric objects gain an attribution property. The README quantifies the cost honestly: about 1.5K brotli'd on top of a standard build it describes as roughly 3K brotli'd. That is a 50 percent increase in library weight for diagnostic fields you may never read. The README's own guidance is to use it only if you are actually consuming those features.
This is a reasonable design, but it puts a decision in front of you that many teams get wrong in both directions. Shipping the attribution build to every user because it might be useful later is waste. Shipping the standard build and then having no way to explain a regression means a second deploy cycle before you can investigate. If your metrics are already healthy, the standard build is the correct default.
Where web-vitals is the wrong tool
The library measures; it does not fix. There is no reporting UI, no alerting, no historical storage, and no comparison against thresholds beyond whatever rating values the API exposes. If your team has no analytics endpoint or data warehouse to receive the metric objects, installing this produces console output and nothing else.
A second boundary is the soft navigation case. The README lists a usage section for reporting metrics on soft navigations, which tells you that single-page applications need explicit handling rather than getting correct numbers for free. If your app is an SPA and you skip that section, your metrics will describe the initial page load and miss everything that happens after the first client-side route change. That is a silent failure mode: the numbers look plausible, they are just incomplete.
Finally, this is runtime instrumentation shipped to users. If your constraint is a hard performance budget on third-party JavaScript, adding even a 3K brotli'd library plus your own reporting code needs to be justified against what you will actually do with the results. A team that collects field data and never acts on it has added weight for nothing.
How web-vitals differs from Lighthouse and PageSpeed Insights
Lighthouse and the PageSpeed Insights interface run a synthetic audit: a controlled browser loads your page under simulated conditions and produces a score. That is reproducible and useful for catching regressions in a CI pipeline, and it needs no code in your application. The difference is that it measures one machine's experience under one set of conditions, not your users'.
web-vitals takes the opposite approach. It runs in the real browsers of real visitors, so the numbers include their devices, their networks and their interaction patterns. INP in particular only makes sense as a field metric, because it depends on how people actually interact with the page; a synthetic run cannot simulate a frustrated user tapping repeatedly on a slow element. The README's claim that the library matches how Chrome reports to CrUX is precisely about this: your numbers should be comparable to the field data Google already publishes about your origin.
The practical arrangement is to use both. Lighthouse in CI for fast, deterministic feedback on changes, and web-vitals in production for the truth about what users experience. Choosing one and calling it done leaves a real gap either way.
Maintenance, licensing and what upgrading involves
The repository is not archived and the last push was on 2026-09-14, so the project is being worked on. The package.json in the repository lists version 6.2.2, and the repository contains a CHANGELOG.md, which is where breaking changes between major versions would be recorded. The README's CDN examples reference web-vitals@5, so the documentation and the published version are not perfectly in step; check the changelog rather than assuming the examples show the current major.
Upgrade cost is low by design. The public surface is a set of onX functions plus a metric object, and the build variants are separate entry points in the exports map. A major version bump is the moment to re-read the changelog, because metric semantics can change with browser behaviour: INP replaced First Input Delay as a Core Web Vital, and a library tracking the official definition has to follow.
Licensing is Apache-2.0, stated in the repository's LICENSE file and in the README's License section. That is a permissive licence with an explicit patent grant and a requirement to preserve notices. It is compatible with commercial and closed-source use, but this is a description of the licence text, not legal advice; your own counsel should confirm obligations if you redistribute the built files rather than bundling them into an application.
Editorial conclusion
Adopt web-vitals if you need field data that matches what Chrome reports to CrUX and PageSpeed Insights, and you already have somewhere to send the numbers. Do not adopt it if you only want a one-off lab score: Lighthouse or the PageSpeed Insights UI answers that question without shipping code to users. Before rolling it out, verify which build you need (standard or attribution), confirm your analytics endpoint accepts the metric objects, and check whether your project requires the soft-navigation support, since that behaviour is opt-in rather than default.
Frequently asked questions
What are web vitals and what does the web-vitals library measure?
Web Vitals are the performance metrics Google uses to describe user experience. The web-vitals library measures the Core Web Vitals (CLS, INP, LCP) plus FCP and TTFB on real users, matching how Chrome measures them.
How do I use web-vitals in React?
The library is framework-agnostic: install it from npm and call the onLCP, onINP and onCLS functions with a callback, typically inside a useEffect so registration happens once after mount. The README does not ship a React-specific wrapper.
What is the difference between the standard and attribution builds of web-vitals?
The attribution build adds an attribution property to each metric object with diagnostic data for finding the root cause of a poor score. It is about 1.5K brotli'd larger than the standard build, so the README recommends it only if you actually use those fields.
Does web-vitals need to load early in the page?
No. The library uses the buffered flag for PerformanceObserver, so it can read performance entries that occurred before it loaded. The README states it should generally be deferred until after other user-impacting code has loaded.
What licence does web-vitals use?
Apache-2.0, per the LICENSE file in the repository and the README's License section.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/googlechrome-web-vitals)