GitHub Packages · MIT · Node ≥ 18

vitals-pulse

Fail CI when Core Web Vitals get worse: LCP, CLS and an INP proxy, gated on thresholds and on regressions against your baseline.

Fail CI on Core Web Vitals: reads Lighthouse, Lighthouse CI, PageSpeed Insights and web-vitals reports (or runs Lighthouse), takes the median per URL and gates LCP, CLS, TBT (lab INP proxy), INP, FCP and TTFB against thresholds and a baseline. SARIF, GitHub Action. Zero deps.

  1. 1 Point the @mrlouiswright scope at GitHub Packages in .npmrc

    @mrlouiswright:registry=https://npm.pkg.github.com
  2. 2 Install

    npm i -D @mrlouiswright/vitals-pulse
Installing from GitHub Packages needs a token

Published to GitHub Packages, not npmjs.com. GitHub Packages needs a GitHub token with read:packages to install, even for public packages: add //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} to .npmrc or run npm login --registry=https://npm.pkg.github.com.

~/project — zsh
$ npx vitals-pulse test/fixtures/badvitals-pulse — test/fixtures/bad4 URL(s), 4 run(s); worst LCP 4.80 s, CLS 0.310, TBT 750 ms HIGH     test/fixtures/bad/broken.json test/fixtures/bad/broken.json: Unexpected end of JSON input [vitals-report-error]HIGH     test/fixtures/bad/lhr-error.json https://example.com/down: ERRORED_DOCUMENT_REQUEST: Lighthouse was unable to reliably load the page (Status code: 500) [vitals-report-error]HIGH     test/fixtures/bad/lhr-home.json https://example.com/: LCP 4.80 s is poor (> 4.00 s) [vitals-poor]HIGH     test/fixtures/bad/lhr-home.json https://example.com/: CLS 0.310 is poor (> 0.250) [vitals-poor]HIGH     test/fixtures/bad/lhr-home.json https://example.com/: TBT (INP proxy) 750 ms is poor (> 600 ms) [vitals-poor]HIGH     test/fixtures/bad/rum.jsonl https://example.com/app: INP 593 ms is poor (> 500 ms) [vitals-poor]MEDIUM   test/fixtures/bad/lhr-blog.json https://example.com/blog: LCP 3.10 s needs improvement (> 2.50 s) [vitals-needs-improvement]
Sample output from the vitals-pulse README.

Why it exists

Lighthouse tells you a page is slow; it does not stop the pull request that made it slow. vitals-pulse reads the reports you already produce (Lighthouse JSON, Lighthouse CI's .lighthouseci folder, PageSpeed Insights API responses, or web-vitals measurements from your RUM) or runs Lighthouse for you, takes the median of several runs per URL, and fails the build when a metric crosses Google's "poor" or "needs improvement" lines or regresses against a snapshot from your main branch.

CLI, library and GitHub Action. Node.js 18+, zero runtime dependencies (Lighthouse is only needed for --url). By Louis Wright.

Features

Metrics

LCP, CLS, TBT (Total Blocking Time, the lab proxy for INP), INP (from PSI field data or web-vitals), FCP, TTFB and Speed Index, with the standard good/poor thresholds (LCP 2.5 s / 4 s, CLS 0.1 / 0.25, TBT 200 / 600 ms, INP 200 / 500 ms); override any of them.

Stable numbers

multiple runs per URL are combined with the median, like Lighthouse CI; large run-to-run spread is noted.

Regressions

--snapshot saves medians; --baseline fails when a metric gets more than --max-regression percent worse (default 10%) and by more than a meaningful absolute amount (100 ms LCP, 0.02 CLS, 50 ms TBT).

Inputs

Lighthouse/LHCI/PSI JSON, web-vitals JSON or JSON lines (p75 per URL), any mix of files and directories. Failed Lighthouse runs (runtimeError) are reported, not silently skipped.

Optional runner

--url runs an installed lighthouse CLI (desktop or mobile preset) several times per URL.

Usage

# Example

 npx vitals-pulse .lighthouseci

# Example

 npx vitals-pulse reports/ --lcp 2000 --min-score 0.9

# on main

 npx vitals-pulse reports/ --snapshot vitals-base.json

# Example

 npx vitals-pulse reports/ --baseline vitals-base.json --max-regression 5

CI / GitHub Action

permissions:
  contents: read
  security-events: write   # only needed for the SARIF upload
jobs:
  vitals-pulse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with: { persist-credentials: false }
      - uses: MrLouisWright/vitals-pulse@v1
        with:
          path: .lighthouseci
          fail-on: high
          sarif: vitals-pulse.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: vitals-pulse.sarif }

Options & flags

FlagDescription
Gate
--metrics <list>Metrics to gate: lcp, cls, tbt, inp, fcp, ttfb, si (default lcp,cls,tbt,inp)
--min-score <0-1>Minimum Lighthouse performance score
Baseline
--baseline <file>Snapshot to compare against (written with --snapshot)
--baseline-optionalDo not fail if the baseline does not exist yet
--max-regression <pct>Allowed regression per metric, in percent (default 10)
--snapshot <file>Write the medians as a snapshot
-u, --url <url>URL to measure, repeatable
--runs <n>Runs per URL, median is used (default 3)
--preset <p>desktop (default) or mobile
Common
-c, --config <file>JSON config (default: .vitals-pulse.json or "vitalsPulse" in package.json)
-f, --format <fmt>text (default) | json | sarif | markdown
--json <file>Also write a JSON report
--sarif <file>Also write SARIF 2.1.0
--markdown <file>Also write Markdown (e.g. for a job summary)
--fail-on <sev>critical | high | medium | low | info | none (default: high)
--list-rulesPrint rules and default severities
--no-fixesHide fix suggestions in text output
--no-colorDisable colours

Full reference, config file format and API in the README.

Support this project

Support open-source security tools

I build free, open-source tools that help developers ship safer websites. Every coffee helps keep them maintained, documented and free for everyone.

Buy me a coffee (opens Stripe in a new tab)

Secure checkout by Stripe

Portrait of Louis Wright

About the author

Louis Wright

Cyber security researcher and software engineer based in the UK.

More tools by Louis Wright

58 tools

Sentinels security scanners & CI gates · 20