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 --file ./dist/index.html
perf-audit https://example.com --fail-on warn # stricter CI gateExit 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
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 | <img> without width/height or CSS aspect-ratio causes layout shift. |
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 |
|---|---|---|
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. |
inline-script-total |
info | Total inline JS over 50KB. |
→ Background reading: Dynamic imports & route-based splitting, 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. |
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.
# .github/workflows/perf-audit.yml
- run: npx perf-audit https://staging.example.com --fail-on errorFor PR previews, pair --json with actions/github-script
to post a comment.
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.
MIT © frontend-performance