npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

Readme

crux-vitals

npm version

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 data

How 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 files

Core 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-vitals

Requires 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 --help

Get 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 --history

CLI 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 help

Output

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 detection

Example 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.345s

API

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 fetch and --env-file)
  • CrUX API key

Author

Built by jlhernando

License

ISC