Core Web Vitals Benchmark: Page Speed Competitors, CrUX
Pricing
from $5.00 / 1,000 site with data
Core Web Vitals Benchmark: Page Speed Competitors, CrUX
Core Web Vitals benchmark for page speed competitors, from real Chrome UX Report (CrUX) data: LCP, INP, CLS at p75, pass or fail and weekly history for your site and rivals. A bulk PageSpeed Insights alternative, no API key needed. Pay only for sites with data.
Pricing
from $5.00 / 1,000 site with data
Rating
0.0
(0)
Developer
Solid Fetch
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
12 hours ago
Last modified
Categories
Share
Core Web Vitals Benchmark: Page Speed Competitors from the Chrome UX Report
Compare the real-user site speed of your client with its competitors. Give the actor a list of sites and it returns one row per site from the official Chrome UX Report (CrUX) API: the 75th percentile and the share of good, needs-improvement and poor page loads for Largest Contentful Paint, Interaction to Next Paint, Cumulative Layout Shift, First Contentful Paint and Time to First Byte, a pass or fail verdict for Core Web Vitals, and a rank by LCP within your list (in the run summary). Optional weekly history shows whether a site is getting faster or slower.
The numbers are field data from Chrome users, the same data Google uses for the page experience signal. They are not a lab test of a single page load.
What people use it for
- A client versus competitors table. Put the client and four rivals in
urlsand get one sheet: who is fastest on mobile, who passes Core Web Vitals, who is slowest on interaction. - Monthly agency reports. Run it on a schedule with
includeHistoryon and chart 25 weeks of p75 per metric. - Prospecting. Run a list of leads and keep the ones that fail LCP or INP.
- Checking a fix. Compare the same site before and after a release by the
collectionPerioddates and the history arrays.
How to use it
You don't need an API key: the actor uses its own Chrome UX Report API key by default. That key's quota (150 queries per minute) is shared by everyone who runs the actor, so for long lists or frequent schedules you can paste your own free key in apiKey: in the Google Cloud console enable "Chrome UX Report API" for a project and create an API key under Credentials. Your key is stored as a secret input and is never written to the log, the dataset or error reports.
{"urls": ["client-site.com", "competitor-a.com", "https://www.competitor-b.com/"],"level": "origin","formFactor": "PHONE","includeHistory": true,"historyPeriods": 25}
Input
| Field | What it does |
|---|---|
urls | Domains, origins or page URLs, up to 200. A bare domain becomes https://<domain>. With level origin, a page URL is cut down to its origin, and when CrUX has no data for that origin the actor tries up to two sibling origins, only after a 404: the https version of the same host (for an http:// input), then the same host with or without www. (https). The first one with data is reported in queried and the row gets queriedFallback: true, so a URL copied from the browser (https://www.allegro.pl/kategoria/telefony) or an http:// one still finds the site. A sibling that turns out to be a site another input already gave is a free repeat. A list entry with several sites ("web.dev, wp.pl") is split at commas and line breaks into separate inputs, and the run log says so once. A comma inside a page path or query (https://shop.com/c/shoes,boots) stays part of that URL unless a host name (b.com/x) or a space follows the comma. An empty entry gets its own free row with the error empty input. |
level | origin (default) uses data of all pages of the site. url queries each page on its own, which has data only for pages with enough visits. |
formFactor | ALL (default), PHONE, DESKTOP or TABLET. |
includeHistory | Adds the p75 per collection period from the CrUX History API. Default off. |
historyPeriods | Number of periods for the history, 1 to 40, default 25. |
apiKey | Optional. Your own Chrome UX Report API key (secret). Left empty, the actor's own key is used. |
An unknown field name stops the run, so a typo cannot turn into a run that ignores a setting.
Output
One row per input, in input order.
| Field | Meaning |
|---|---|
input | The text you gave. |
level, queried, formFactor | What was asked: the normalised origin or URL, and the device filter. queried is null for an invalid input. |
queriedFallback | true when CrUX had no data for the origin as typed and queried is a sibling origin (https, or with or without www.) that has. Otherwise false. |
collectionPeriod | firstDate and lastDate of the 28-day window the numbers cover. |
largest_contentful_paint, interaction_to_next_paint, cumulative_layout_shift, first_contentful_paint, experimental_time_to_first_byte | Each is { p75, good, needsImprovement, poor } or null when CrUX has no data for that metric. LCP, INP and TTFB are in milliseconds, CLS has no unit. The three shares add up to about 1. |
passesCoreWebVitals | true when LCP p75 <= 2500, INP p75 <= 200 and CLS p75 <= 0.1. false if any is over. null when any of the three is missing. |
duplicateOf | null, or the earlier input whose result this row repeats. Two inputs that normalise to the same site (for example web.dev and https://web.dev/x at level origin) are queried once and charged once; the repeat is saved free. |
history | With includeHistory: collectionPeriods (oldest first) and p75 arrays per metric, with null for a period without data. |
trend | With includeHistory: per metric { trend, trendPercent }, the mean p75 of the last 4 weekly periods against the 4 before. improving or worsening when it moved by more than 5 percent (lower is better for every metric), otherwise stable; trendPercent is the change in percent (null when the earlier mean is 0). A metric is null with fewer than 8 periods (historyPeriods 8 or more) or fewer than 2 values in a window. |
lcpBreakdown | Why LCP is slow: p75 in ms of ttfb, loadDelay, loadDuration and renderDelay, the four subparts of the LCP of image elements, from the same CrUX call. Each p75 is taken on its own, so the four do not add up to the LCP p75. Null when CrUX has no subpart data (for example a site whose LCP is mostly text). Not part of the history. |
lcpResourceType | Share of page loads by the type of the LCP element, such as { "text": 0.72, "image": 0.28 }. Null when CrUX has none. Read it with lcpBreakdown: the subparts describe only the image share. |
bestInRun, gapToBest, wins | The comparison inside your list, see below. |
historyError | Why history is missing although the row has data, otherwise null. |
error | null, or the reason: no data for this origin, no data for this url, invalid url: ..., empty input, quota: ..., an API error, or not checked after repeated failures. |
source | Attribution, see below. |
Example row (shortened):
{"input": "web.dev","level": "origin","queried": "https://web.dev","formFactor": "PHONE","collectionPeriod": { "firstDate": "2026-09-14", "lastDate": "2026-10-11" },"largest_contentful_paint": { "p75": 1800, "good": 0.82, "needsImprovement": 0.12, "poor": 0.06 },"passesCoreWebVitals": true,"error": null,"source": "Chrome UX Report (CrUX) by Google, CC BY 4.0, https://developer.chrome.com/docs/crux"}
The values above only show the shape.
Comparison inside the run
For LCP, INP and CLS the actor finds the best site of your list (lowest p75; the first one on a tie; a metric that fewer than two sites have gets no best). SUMMARY holds it as bestInRun: { "largest_contentful_paint": { "input": "a.com", "p75": 1800 }, ... }. Rows carry the same bestInRun, plus gapToBest per metric ({ absolute, percent }, percent of the best value, null when the best value is 0) and wins, the list of metrics where the row is best (ties included). The comparison costs no extra requests.
Rows are saved as they are checked, so a row cannot know the best site of the run before the whole list is queried. The actor therefore chooses by list size:
- 50 inputs or fewer: all inputs are queried first, then the rows are saved in input order, each with its comparison. Nothing is charged before the last query. If the run stops halfway (for example the key is rejected), the rows already queried are still saved and charged before the run fails, and their comparison covers the inputs checked so far.
- More than 50 inputs: rows are saved one by one as before, so a stop keeps them. Their
bestInRun,gapToBestandwinsare null (a gap against the best site known so far would change with every row). UsebestInRuninSUMMARYand compute the gaps from the dataset.
An input with no data, an invalid input or a failed one has these fields null. A repeated input gets the same comparison as the input it repeats.
LCP rank
The LCP ranking of your list (1 for the lowest p75, equal values share a rank) is not a column of the dataset, because rows are saved one by one as they are checked. It is in the ranking list of the run's key-value store record SUMMARY, which also holds the inputs without data, the repeated and the failed ones. Rows are saved as they are checked. If the run stops (for example the key is rejected half way), the rows already saved stay in the dataset and are charged, and the failure message says how many. A run that restarts continues after the saved inputs and does not charge them again.
Pricing
Pay per event, prices on the Pricing tab.
| Event | Charged for |
|---|---|
domain-checked | Each row that has CrUX data. |
Not charged: rows with no data for the site, invalid and empty inputs, quota or API errors, inputs that were not checked, and anything past your maximum cost per run. History is part of the same row, not a second charge.
The final status line counts bad input apart from API trouble, for example 12 of 15 inputs had data, 1 had no CrUX data, 3 invalid, 2 failed. Invalid (including empty) inputs are mistakes in the list; failed ones are quota, API or network errors.
Limits
- No data is normal. CrUX only has sites and pages that Chrome users visit often enough. Small sites and most single pages return 404 from the API, which is reported as
no data for this originorno data for this urland is free. Origin level is the one that works for most sites. - Quota. The API allows 150 queries per minute per Google Cloud project, free of charge. The actor sends at most two requests a second, retries 429 and 5xx answers with a wait of 5, 10, 20 and 40 seconds (and honours Retry-After), and does not retry a 404. If three inputs in a row fail that way (429 or 5xx after the retries, network errors, timeouts), the rest are marked
not checked; a run where nothing could be checked because of such failures ends as failed instead of burning more of your quota. Using the same key in other tools at the same time counts against the same quota. - Rejected key. A wrong key, a key without the API enabled or a key with a restriction that excludes this call stops the run with the reason from Google. Nothing is saved or charged.
- Freshness. CrUX updates daily and each number covers the latest 28 days, so two runs on the same day return the same values.
- Form factor.
ALLcombines devices. A site can have phone data and no desktop data. - LCP breakdown is a hint, not a diagnosis.
lcpBreakdowntells which phase (server response, discovery, download, render) is the biggest for image LCPs; it does not name the element or the script. The subparts are only returned for sites with enough image LCPs. - Not a lab test. Field data cannot say which element or script is slow. Use it to see who is ahead and then investigate with a lab tool.
Related actor
Competitor Ad Monitor for Google Ads shows every Google ad the same competitors run, and can return only the new ones each day.
Attribution
CrUX datasets by Google are licensed under a Creative Commons Attribution 4.0 International License (CC BY 4.0). Every row carries this in its source field, and you should keep that attribution when you publish the data. The actor is not affiliated with Google.
When a run fails, an error report with the error message and the shape of the input (which fields are set, how long the lists are), never your input values, your API key or your results, is sent to the developer to fix bugs.