A fast, browserless CLI that audits a URL's HTML and response headers for common frontend performance issues — Core Web Vitals risks, caching mistakes, render-blocking resources, font and image pitfalls — and links each finding to a deep-dive explainer on frontend-performance.com.
No Chromium, no headless browser, no Lighthouse. Just fetch + an HTML parser,
so it runs in under a second and fits in any CI step.
Not yet on npm. Run directly from GitHub with npx:
npx github:frontend-performance/perf-audit https://example.comOr clone and link locally:
git clone https://github.com/frontend-performance/perf-audit.git
cd perf-audit
npm install # builds via the prepare script
npm link # exposes the `perf-audit` binaryRequires Node.js 20+.
perf-audit https://example.com
perf-audit https://example.com --json > report.json
perf-audit https://example.com --markdown >> "$GITHUB_STEP_SUMMARY"
perf-audit --file ./dist/index.html
perf-audit https://example.com --fail-on warn # stricter CI gateOutput modes:
- default — colorized human-readable report
--json— machine-readable JSON (includesscore,grade, and severitycounts)--markdown(alias--md) — a Markdown report with a findings table, ideal for a PR comment or a GitHub Actions Step Summary
Exit codes:
0— no findings at the configured severity1— at least one finding at or above--fail-on(default:error)2— bad arguments or fetch failure
Every run prints a deterministic score and letter grade at the top:
Grade F 12/100 4 error · 5 warn · 3 info
The score starts at 100 and subtracts a fixed weight per finding:
| Severity | Points |
|---|---|
| error | −15 |
| warn | −5 |
| info | −1 |
The total is clamped to 0–100, then graded: A ≥ 90, B ≥ 80, C ≥ 70,
D ≥ 60, F < 60. Because the weights are fixed and the checks are static, the
same HTML always yields the same score — safe to assert on in CI.
Each rule links to a longer explanation on frontend-performance.com — the goal is to explain why a finding matters, not just flag it.
| Rule | Severity | Description |
|---|---|---|
missing-viewport |
error | No <meta name="viewport"> — mobile browsers use a 980px virtual viewport, inflating CLS. |
lazy-hero-image |
error | First <img> has loading="lazy", harming LCP. |
img-missing-dimensions |
warn | Non-lazy <img> without width/height or CSS aspect-ratio causes layout shift. |
lazy-img-missing-dimensions |
warn | Lazy <img> without dimensions — arrives late and shifts layout (strong CLS signal). |
eager-below-fold |
info | Images with loading="eager" late in the document waste bandwidth. |
high-ttfb |
warn | TTFB > 800ms is the Core Web Vitals "poor" threshold. |
→ Background reading: Core Web Vitals thresholds, Reducing CLS, Measuring LCP.
| Rule | Severity | Description |
|---|---|---|
hero-image-no-fetchpriority |
info | First (non-lazy) <img> has no fetchpriority="high" — the LCP image could start sooner. |
img-no-modern-format |
info | Raster <img> (JPEG/PNG) with no AVIF/WebP fallback via <picture> or srcset. |
img-no-responsive-srcset |
info | Large <img> (width ≥ 1000) with no srcset/sizes — small screens overfetch. |
→ Background reading: Image CDNs & fetchpriority, Serving AVIF & WebP with fallbacks, Responsive images with srcset & sizes, Lazy-loading without hurting LCP.
| Rule | Severity | Description |
|---|---|---|
render-blocking-script |
error | <script src> in <head> without async / defer / type="module". |
large-inline-script |
warn | Inline <script> over 4KB — can't be cached across navigations. |
many-third-party-scripts |
warn | More than 10 distinct third-party script hosts — main-thread and connection overhead. |
inline-script-total |
info | Total inline JS over 50KB. |
→ Background reading: Dynamic imports & route-based splitting, ESM vs CommonJS module formats, Tree shaking & dead-code elimination.
| Rule | Severity | Description |
|---|---|---|
many-blocking-stylesheets |
warn | More than 3 render-blocking stylesheets. |
large-inline-style |
warn | Inline <style> over 50KB — critical CSS gone too far. |
css-import-in-style |
warn | @import inside inline <style> — discovered late, serializes the CSS download. |
font-display-missing |
warn | @font-face without font-display causes FOIT. |
preload-font-missing-crossorigin |
error | Preloaded fonts need crossorigin or they fetch twice. |
| Rule | Severity | Description |
|---|---|---|
missing-preconnect |
info | A third-party origin is referenced multiple times without preconnect/dns-prefetch. |
no-compression |
error | HTML response has no Content-Encoding. |
compression-gzip-only |
info | HTML uses gzip — Brotli usually saves another 15–25%. |
html-no-cache-control |
warn | No Cache-Control header on the HTML response. |
html-immutable |
warn | HTML marked immutable — updates won't reach users. |
html-no-swr |
info | Cache-Control is missing stale-while-revalidate. |
no-hsts |
info | No Strict-Transport-Security header. |
→ Background reading: HTTP Cache-Control headers explained, Stale-while-revalidate, CDN edge caching.
Run it straight from GitHub with npx — no publish step needed. Pipe --markdown
into $GITHUB_STEP_SUMMARY to render the findings table right on the run summary:
# .github/workflows/perf-audit.yml
name: perf-audit
on: [push, pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: 20
# Write the report to the job summary, but don't fail the step yet…
- run: npx github:frontend-performance/perf-audit https://staging.example.com --markdown --fail-on never >> "$GITHUB_STEP_SUMMARY"
# …then gate the build on errors (or use --fail-on warn to be stricter).
- run: npx github:frontend-performance/perf-audit https://staging.example.com --fail-on errorFor a PR comment, capture the Markdown and post it with
actions/github-script, or capture --json
and build your own message.
perf-audit is a static analyzer. It can't measure runtime metrics that
require a real browser — LCP timing, INP, layout-shift score, JS execution
cost. For those, run Lighthouse or PSI in addition.
The trade-off: static analysis is fast, deterministic, and dependency-free, so it's a great first gate. Most regressions show up here first.
Every finding deep-links into one of the four performance clusters on frontend-performance.com:
- Core Web Vitals measurement — LCP, CLS, INP, and how the thresholds are defined.
- Advanced caching strategies & CDN architecture — cache-control, edge caching, stale-while-revalidate.
- JavaScript bundle optimization & code splitting — tree shaking, dynamic imports, module formats.
- Image & media optimization — modern formats, responsive images, fetchpriority, lazy loading.
MIT © frontend-performance