# Bulk Core Web Vitals Monitor (PageSpeed + CrUX) (`mrdexter4/core-web-vitals-monitor`) Actor

Bulk PageSpeed + CrUX monitor for hundreds of URLs. Get alerted when your speed app stops working: history, regression alerts, a warning when Lighthouse scores well but real users fail Core Web Vitals, competitor benchmarks, Slack/webhook alerts. No monthly plan: pay per URL.

- **URL**: https://apify.com/mrdexter4/core-web-vitals-monitor.md
- **Developed by:** [Mateus Correia](https://apify.com/mrdexter4) (community)
- **Categories:** SEO tools, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.60 / 1,000 url auditeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Core Web Vitals Monitor: bulk PageSpeed & CrUX checker (GTmetrix alternative)

**Get alerted when your speed app or plugin stops working.**

Audit hundreds of URLs with **PageSpeed Insights** and **CrUX** real-user data (with a free Google API key) in one run, track **Core Web Vitals** (LCP, INP, CLS), get Slack alerts on regressions and benchmark competitors. It flags pages that **score 90+ in Lighthouse while real Chrome users fail Core Web Vitals**. This can mean a speed plugin or app is optimising for the PageSpeed test rather than for real visitors, or that the problem only shows on real devices.

**A GTmetrix alternative with no monthly plan:** $0.008 per URL per device, competitor URLs included, plus $0.01 per own URL × device benchmarked. Google's PSI quota still applies (25,000 requests/day with a free key).

**Use cases**

- **SEO consultants:** weekly client reports with regression alerts.
- **Shops using a speed app (NitroPack, WP Rocket):** daily checks for real users.
- **E-commerce teams:** benchmark key templates against competitors.
- **Developers:** check every release from CI, n8n, Make or Zapier.

**Sample output** (shortened, real coolblue.nl run):

```json
{
  "url": "https://www.coolblue.nl/",
  "strategy": "mobile",
  "performanceScore": 61,
  "field": { "source": "crux-api", "lcpMs": 2002, "inpMs": 202, "cls": 0.01 },
  "coreWebVitalsAssessment": "failed",
  "labFieldMismatch": false,
  "failingFieldMetrics": [{ "metric": "INP", "p75": 202, "rating": "needs-improvement" }]
}
```

### What does it do?

- **Bulk PageSpeed Insights (v5).** Mobile, desktop or both, with retries and rate-limit handling.
- **CrUX field data.** Real-user p75 LCP, INP, CLS, FCP and TTFB from the official CrUX API. Falls back to origin-level data when a URL has too little traffic; `field.scope` says `url` or `origin`. Optionally adds 6-month CrUX history.
- **Core Web Vitals assessment** (passed / failed / insufficient data) on field data.
- **Threshold checks** for LCP, INP, CLS and the performance score, using your thresholds. Field data is preferred; each check names its source.
- **History and regression detection.** Each run is compared with the previous one for the same URL and device, with deltas and a `regressions` / `improvements` list (for example "Performance score dropped 72 → 60" or "Lab LCP worse by 38% (2100ms → 2900ms)").
- **Lab-vs-field mismatch warning.** When the Lighthouse score is 90 or higher but CrUX fails Core Web Vitals (p75 LCP > 2.5 s, INP > 200 ms or CLS > 0.1), the row gets `labFieldMismatch: true`, the failing metrics in `failingFieldMetrics` and a plain-language message in `warnings`. They also appear in the report, `OUTPUT` and alerts.
- **Competitor comparison.** Your rank, the gap to the best competitor's score and LCP, and a side-by-side table in the report.
- **Alerts** to any webhook or Slack Incoming Webhook.
- **Works without an API key**, with limits (see FAQ).

### Input

| Field | Description | Default |
|---|---|---|
| `urls` | URLs to audit (yours) | – |
| `competitorUrls` | Competitor URLs to benchmark against | – |
| `strategy` | `mobile`, `desktop` or `both` | `mobile` |
| `pageSpeedApiKey` | Your Google API key (stored as a secret). Recommended. | – |
| `includeCrux` / `includeCruxHistory` | Real-user data from the CrUX API (needs an API key) | `true` / `false` |
| `engine` | `auto` (PSI, then local Lighthouse when the quota is exhausted), `pagespeed`, `local` | `auto` |
| `alertThresholds` | `{ lcpMs, inpMs, cls, performanceScore }` | 2500 / 200 / 0.1 / 90 |
| `historyMode`, `historyStoreName` | Keep history and compute deltas and regressions | `true` |
| `regressionScoreDrop`, `regressionMetricPct` | What counts as a regression | 5 points / 15% |
| `webhookUrl`, `alertOn` | Where to send alerts and for what (`regressions`, `threshold-failures`, `both`) | – / `regressions` |
| `maxConcurrency`, `locale` | Advanced settings | auto / `en` |

**Free Google API key (2 minutes):** in Google Cloud Console, create a project, enable **PageSpeed Insights API** and **Chrome UX Report API**, create an API key under **Credentials** restricted to those two APIs, and paste it into `pageSpeedApiKey`. The default PSI quota is 25,000 requests per day.

### Output

One dataset row per URL × device, also with `role` (`own` or `competitor`), `status` (`ok` or `error`), full `lab`/`field` metrics, `checks`, `passedAllThresholds`, `topOpportunities`, `engine` and `pageSpeedInsightsUrl`. On the next run of the same URL, `previous`, `delta`, `regressions` and `improvements` are filled in.

`labFieldMismatch` is `false` in the sample because the lab score (61) is below 90. When a page scores 90+ but field data fails, it is `true` and `warnings` explains why, for example: *"Lab score 98 looks good, but real Chrome users fail Core Web Vitals: CLS 0.11 (needs improvement)..."* It is `null` when there is no field data or no lab score.

The key-value store holds `report.html`, `OUTPUT` (run summary, `labFieldMismatches`, alerts) and `COMPARISON`. Download the dataset as CSV, Excel or JSON.

### Schedule recurring Core Web Vitals monitoring

Speed usually slips after launch, so monitoring is where this Actor is most useful:

1. Save your URLs, competitors, API key and `webhookUrl` as an Apify **Task** (one per client or site, each with its own `historyStoreName`).
2. In Apify Console, open **Schedules** and add the task with a cron expression, for example `0 7 * * *` for daily at 07:00 or `0 7 * * 1` for Mondays.
3. `alertOn` defaults to `regressions`, so Slack only hears from it when something got worse. A lab-vs-field mismatch is alerted only when it is new since the previous run.

Use an API key so the engine, and the numbers, stay the same between runs. See [Apify schedules](https://docs.apify.com/actors/running/schedules).

### Use it from n8n, Make and Zapier

Apify has official integrations for all three: connect your Apify account with an API token, then use the Apify app or node to **run an Actor or Task** and **fetch its dataset items**, or trigger a workflow **when a run finishes**.

Typical workflows:

- **Client dashboard:** after each scheduled run, keep rows with `role: "own"` and `status: "ok"` and append `url`, `strategy`, `performanceScore`, field LCP/INP/CLS and `coreWebVitalsAssessment` to a Google Sheet or Looker Studio source.
- **Ticket on regression:** for `role: "own"` rows with a non-empty `regressions` list, create a Jira, Linear or Trello card. For mismatches, use the webhook alerts (new mismatches only, by default), since `labFieldMismatch` stays `true` on every run until the page is fixed.
- **After a deploy:** run the Task for the changed templates and post the result to the release channel.

Guides: [n8n](https://docs.apify.com/integrations/n8n), [Make](https://docs.apify.com/integrations/make), [Zapier](https://docs.apify.com/integrations/zapier).

### Pricing (pay per event)

No subscription, no auto-renew, no monthly plan, no scan quota or page-view tiers. You pay only for audits that run.

| Event | Price |
|---|---|
| URL audited (`url-audited`), per URL × device | **$0.008** |
| Competitor comparison (`competitor-comparison`), per own URL × device benchmarked | **$0.01** |

Competitor URLs are audited too, so each one costs $0.008 per device, on top of the $0.01 comparison per own URL × device.

Examples: a weekly check of 20 URLs on mobile costs about **$0.64 a month**. 100 URLs on mobile and desktop, daily, cost about **$48 a month**. Failed audits are not charged.

Apify's free monthly credit covers hundreds of audits. Set **Maximum cost per run** to cap spend.

### FAQ

**Do I need a Google API key?**
No, but it is recommended, and needed for heavy use. Without a key, Google's keyless quota is shared by everyone and is often exhausted. The Actor then uses its built-in Lighthouse 12 fallback, which gives lab data only and no CrUX field data. With your own free key, results come from Google's PageSpeed Insights API itself, plus CrUX real-user data.

**Why do scores differ from my last run?**
Lighthouse lab scores vary by about ±5 points between runs, so the default regression thresholds ignore small changes and field data (CrUX, 28-day p75) is preferred for pass/fail.

**Why is INP empty?**
INP can only be measured on real users, so it comes only from CrUX. Low-traffic pages have no CrUX data; the origin-level value is used when available.

**My speed plugin gives me 95+ in PageSpeed. Why does this flag a mismatch?**
The PageSpeed score is one lab test on a simulated device. Core Web Vitals pass/fail comes from real Chrome users over 28 days. Some plugins improve the lab test (for example by delaying scripts) without helping visitors. When the two disagree, trust the field data and test on a real phone. Field data lags by up to 28 days, so give a change a few weeks.

**How is this different from GTmetrix?**
It is pay per use with no monthly plan, audits hundreds of URLs per run, and adds CrUX data, regression alerts and competitor benchmarks. It does not offer GTmetrix's choice of test locations or its waterfall and video views.

**Can I send alerts to Slack?**
Yes. Paste a Slack Incoming Webhook URL into `webhookUrl`. The payload includes a Slack `text` field and structured alerts.

### Limitations

- The built-in Lighthouse fallback runs on Apify's servers. Mobile scores can be lower than PSI's (different CPU).
- Sites with aggressive bot protection may block the fallback. PSI with an API key is usually not blocked.
- CrUX data exists only for URLs and origins with enough Chrome traffic.
- The PSI API has a limit of about 60 s per audit. Very slow pages can time out and are reported as failed, not charged.

### Keywords

Core Web Vitals, Core Web Vitals monitor, PageSpeed checker, bulk PageSpeed Insights, CrUX, Chrome UX Report, LCP INP CLS, site speed monitoring, GTmetrix alternative, competitor speed comparison.

# Actor input Schema

## `urls` (type: `array`):

Pages to test (homepage, category, product, landing pages …). One PageSpeed audit per URL per strategy.

## `strategy` (type: `string`):

Mobile, desktop, or both (both = two audits per URL).

## `competitorUrls` (type: `array`):

Competitor pages to benchmark against. Each is audited too, and every own URL gets a rank and its gap to the best competitor.

## `pageSpeedApiKey` (type: `string`):

Free key from Google Cloud with the PageSpeed Insights API and Chrome UX Report API enabled. Without a key the Actor uses Google's tiny shared keyless quota and falls back to running Lighthouse itself (lab data only, no CrUX).

## `includeCrux` (type: `boolean`):

Query the Chrome UX Report API for 28-day p75 LCP, INP, CLS, FCP and TTFB (URL level, falling back to origin). Needs an API key; skipped gracefully otherwise.

## `includeCruxHistory` (type: `boolean`):

Also fetch ~25 weekly p75 data points from the CrUX History API (LCP, INP, CLS) to show trends. Needs an API key.

## `engine` (type: `string`):

Auto = PageSpeed Insights API, falling back to local Lighthouse when Google's quota is exhausted. PageSpeed only = never fall back. Local = always run Lighthouse inside the Actor (no Google quota, lab data only).

## `alertThresholds` (type: `object`):

Pass/fail per metric. Defaults are Google's "good" thresholds. LCP/CLS use field data when available, else lab; INP is field-only; performance score is lab.

## `historyMode` (type: `boolean`):

Store each result in a named key-value store and output deltas vs the previous run, plus regressions/improvements. Schedule the Actor daily or weekly to monitor.

## `historyStoreName` (type: `string`):

Named key-value store used for history. Use different names for separate projects/clients.

## `regressionScoreDrop` (type: `integer`):

Flag a regression when the performance score drops by at least this many points vs the previous run.

## `regressionMetricPct` (type: `integer`):

Flag a regression when LCP, TBT, CLS or INP gets worse by at least this percentage (with small absolute minimums to ignore noise).

## `webhookUrl` (type: `string`):

POST a JSON alert (with a Slack-compatible "text" field) when there are alerts. Works with Slack incoming webhooks, Discord (/slack), Zapier, Make and n8n.

## `alertOn` (type: `string`):

Which events trigger the webhook.

## `maxConcurrency` (type: `integer`):

Parallel PageSpeed requests. Default 6 with an API key, 2 without. Local Lighthouse runs one at a time.

## `locale` (type: `string`):

Language of opportunity titles from PageSpeed Insights (e.g. en, nl, de).

## Actor input object example

```json
{
  "urls": [
    {
      "url": "https://web.dev/"
    }
  ],
  "strategy": "mobile",
  "includeCrux": true,
  "includeCruxHistory": false,
  "engine": "auto",
  "alertThresholds": {
    "lcpMs": 2500,
    "inpMs": 200,
    "cls": 0.1,
    "performanceScore": 90
  },
  "historyMode": true,
  "historyStoreName": "cwv-monitor-history",
  "regressionScoreDrop": 5,
  "regressionMetricPct": 15,
  "alertOn": "regressions",
  "locale": "en"
}
```

# Actor output Schema

## `results` (type: `string`):

One item per URL per strategy with lab + field metrics, threshold checks and deltas.

## `report` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        {
            "url": "https://web.dev/"
        }
    ],
    "alertThresholds": {
        "lcpMs": 2500,
        "inpMs": 200,
        "cls": 0.1,
        "performanceScore": 90
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("mrdexter4/core-web-vitals-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": [{ "url": "https://web.dev/" }],
    "alertThresholds": {
        "lcpMs": 2500,
        "inpMs": 200,
        "cls": 0.1,
        "performanceScore": 90,
    },
}

# Run the Actor and wait for it to finish
run = client.actor("mrdexter4/core-web-vitals-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    {
      "url": "https://web.dev/"
    }
  ],
  "alertThresholds": {
    "lcpMs": 2500,
    "inpMs": 200,
    "cls": 0.1,
    "performanceScore": 90
  }
}' |
apify call mrdexter4/core-web-vitals-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mrdexter4/core-web-vitals-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/NPqVldLMZXVQZQAhq/builds/uyu1fbWxkAudPBekA/openapi.json
