crux-vitals
v3.0.0
Published
Zero-dependency CrUX API client for batch Core Web Vitals analysis — LCP, CLS, INP, FCP, TTFB with history and origin queries
Maintainers
Keywords
Readme
crux-vitals
Zero-dependency Node.js client for the Chrome User Experience Report (CrUX) API. Batch-analyze Core Web Vitals and real-user field performance data across hundreds of URLs with automatic rate limiting, retries, and CSV/JSON export.
urls.csv ──> [ crux-vitals ] ──> JSON + CSV results
|
| CrUX API
| (batch requests)
v
Google's real-user
performance dataHow it works
You provide crux-vitals does You get
─────────── ────────────────── ──────────────
A list of URLs ---> Batches them (20/req) ---> P75 metrics
Calls CrUX API Benchmark status
Handles rate limits Aggregate summary
Retries on failure Trend analysis
Concurrent workers JSON + CSV filesCore Web Vitals (with benchmarks):
| Metric | What it measures | Good | Poor | |--------|-----------------|------|------| | CLS | Visual stability | ≤ 0.1 | > 0.25 | | LCP | Loading speed | ≤ 2.5s | > 4.0s | | FCP | First paint | ≤ 1.8s | > 3.0s | | INP | Interactivity | ≤ 200ms | > 500ms | | TTFB | Server response | ≤ 800ms | > 1.8s |
Additional metrics (p75, no official thresholds):
| Metric | What it measures | |--------|-----------------| | RTT | Network round-trip time | | LCP_TTFB | LCP image time to first byte | | LCP_LoadDelay | LCP image resource load delay | | LCP_LoadDuration | LCP image resource load duration | | LCP_RenderDelay | LCP image element render delay |
Fraction metrics (distribution breakdowns):
| Metric | Keys | |--------|------| | Navigation types | navigate, navigate_cache, reload, restore, back_forward, back_forward_cache, prerender | | LCP resource type | image, text | | Form factors | phone, desktop, tablet (only when no device filter) |
Installation
npm install crux-vitalsRequires Node.js 20.6+ and a CrUX API key.
Use as a library
import { CrUX } from 'crux-vitals';
const crux = new CrUX('your-api-key');
// Single URL
const result = await crux.query('https://example.com');
// { url, device, CLS, LCP, FCP, INP, TTFB, ... }
// Batch (auto-handles rate limits + batching)
const { results, noData, errors } = await crux.queryAll(urls, {
device: ['PHONE', 'DESKTOP'],
includeHistogram: true,
});
// results: formatted records in input order
// noData: [{ url, device }] URLs without CrUX data
// errors: [{ url, device, error }] API/network failures
// Origin-level (aggregated across all pages)
const origin = await crux.queryOrigin('https://example.com');
// Historical trends (default 25 periods, max 40)
const trend = await crux.history('https://example.com', { periods: 40 });
// { url, CLS_timeseries: [0.06, 0.08, ...], LCP_timeseries: [...], ... }
// Aggregate summary across batch results
const summary = CrUX.summarize(results);
// { CLS: { good: 10, poor: 2, median, p75, p90, worst: [...] }, cwvPass: { count, pct } }
// Detect trends in history results
const trends = CrUX.detectTrends(historyResults);
// [{ url, device, trends: { LCP: { trend: 'regressing', changePct: 15 }, ... } }]
// Evaluate any value against benchmarks
CrUX.evaluate('LCP', 2300);
// { p75: 2300, status: 'GOOD', diff: 200 }
CrUX.BENCHMARKS;
// { CLS: { good: 0.1, poor: 0.25 }, LCP: { good: 2500, poor: 4000 }, ... }Constructor options
const crux = new CrUX('key', {
batchSize: 20, // URLs per API request (1-20)
batchDelay: 1000, // ms between batches (adaptive, backs off on 429)
concurrency: 1, // parallel batch workers
retries: 3, // retry attempts per request
});CLI usage
# Install globally
npm install -g crux-vitals
# Or use npx
npx crux-vitals --helpGet your free CrUX API key, then create a urls.csv file with a url column:
url
https://example.com/
https://example.com/about/# Pass key as flag
crux-vitals --key YOUR_API_KEY
# Or set as env var
export KEY=YOUR_API_KEY
crux-vitals
# With options
crux-vitals --key YOUR_API_KEY --device PHONE,DESKTOP --histogram
# Tune throughput
crux-vitals --key YOUR_API_KEY --concurrency 2 --delay 500
# History mode with trend detection
crux-vitals --key YOUR_API_KEY --historyCLI options
--file <path> Input CSV (default: urls.csv)
--folder <name> Output folder prefix (default: crux-results)
--device <types> PHONE,DESKTOP,TABLET (default: PHONE)
--key, -k <key> CrUX API key (or set KEY env var)
--origin Origin-level data (instead of per-URL)
--history Trend data with regression detection
--histogram Include % good/NI/poor distribution
--batch-size <n> Requests per batch (default: 20, max 20)
--delay <ms> Delay between batches (default: 1000, adaptive)
--concurrency <n> Parallel batch workers (default: 1)
--retries <n> Max retries per batch (default: 3)
--help, -h Show helpOutput
crux-results_2026-04-17T21-54-01/
cwv-record-phone+desktop.json Full results with all metrics
cwv-record-phone+desktop.csv Spreadsheet-ready version
summary.json Aggregate stats per metric
errors.csv Failed requests (if any)
no-data-urls.csv URLs without CrUX data (if any)
urls.csv Copy of your input
# History mode also generates:
cwv-history-phone.json Timeseries data
trends.json Regression/improvement detectionExample output
100 URLs [Record API, URL level, PHONE]
Batch 1/5...
Batch 2/5...
Batch 3/5...
Batch 4/5...
Batch 5/5...
── CWV Summary ──
CLS 99% good (93 good, 1 NI, 0 poor) median=0.01, p75=0.01, p90=0.02
LCP 93% good (87 good, 7 NI, 0 poor) median=2196, p75=2315, p90=2448
FCP 18% good (17 good, 77 NI, 0 poor) median=1958, p75=2061, p90=2164
INP 0% good (0 good, 85 NI, 9 poor) median=420, p75=463, p90=493
TTFB 0% good (0 good, 76 NI, 18 poor) median=1686, p75=1754, p90=1852
CWV Pass: 0/94 (0%)
Total time: 3.345sAPI
new CrUX(apiKey, config?)
Creates a new CrUX instance. Validates config ranges (throws RangeError on invalid values).
Instance methods
| Method | Description |
|--------|-------------|
| query(url, options?) | Query a single URL |
| queryOrigin(origin, options?) | Query a single origin |
| queryAll(urls, options?) | Batch query multiple URLs |
| history(url, options?) | Get history for a URL |
| historyOrigin(origin, options?) | Get history for an origin |
| historyAll(urls, options?) | Batch history for multiple URLs |
Static methods
| Property/Method | Description |
|----------------|-------------|
| CrUX.BENCHMARKS | Frozen object with thresholds for CLS, LCP, FCP, INP, TTFB |
| CrUX.evaluate(metric, value) | Evaluate a p75 value against its benchmark |
| CrUX.summarize(results) | Aggregate stats: median/p75/p90, pass/fail, worst offenders |
| CrUX.detectTrends(historyResults, options?) | Detect regressions/improvements in history data |
Batch result shape
interface BatchResult<T> {
results: T[]; // Formatted records in input order
noData: { url, device }[]; // URLs without CrUX data
errors: { url, device, error }[]; // API/network/parse failures
}Requirements
- Node.js 20.6+ (uses native
fetchand--env-file) - CrUX API key
Author
Built by jlhernando
License
ISC
