Performance Budgets
Collect Core Web Vitals during rendering, enforce budgets, and see performance regressions correlated with the visual diff for the same page.
Performance Budgets
Frontguard collects performance metrics during the same render it uses for visual testing, checks them against budgets you set, and — the differentiator — correlates a budget violation with the visual diff for the same page. So a report doesn't just say "this page shifted"; it says "this page shifted and is over its LCP budget."
Enabling it
Add the perf-budgets plugin and define your budgets:
import { createPerfBudgetPlugin } from '@frontguard/cli/plugins';
export default {
baseUrl: 'http://localhost:3000',
plugins: [
createPerfBudgetPlugin({
budgets: {
lcp: 2500, // Largest Contentful Paint (ms)
cls: 0.1, // Cumulative Layout Shift
ttfb: 600, // Time to First Byte (ms)
maxPageWeight: 1024, // KB
maxRequests: 60,
},
failOnBudgetExceeded: false,
// Run-over-run regression tracking (optional)
trackRegressions: true,
regressionThreshold: 0.2, // flag metrics >20% worse than last run
}),
],
};Budgets
| Budget | Unit | Measures |
|---|---|---|
lcp | ms | Largest Contentful Paint |
cls | — | Cumulative Layout Shift |
fid | ms | First Input Delay |
tbt | ms | Total Blocking Time (estimated) |
ttfb | ms | Time to First Byte |
maxPageWeight | KB | Total transferred page weight |
maxRequests | count | Number of resource requests |
Set failOnBudgetExceeded: true to fail the run when any budget is breached.
Perf ↔ visual correlation
When a route both has a visual diff and breaches a perf budget, the two are joined in the report:
- Inline — each regression/warning in the PR comment gets a
⚡ Performance — over budget: lcp 3.20s > 2.50snote right inside its details block. - Summary — a standalone ⚡ Performance budgets section lists every violation across the run (route, viewport, metric, actual, budget).
The same data is available in the console and HTML reports, and on
RunResult.perf in the JSON output for programmatic use.
Run-over-run regressions
Static budgets answer "is this page within target?" Regression tracking answers
a sharper question: "did this page get slower than it was last time?" Enable
trackRegressions and Frontguard persists each run's metrics (under historyDir,
default .frontguard/perf-history) and flags any metric that degraded by more
than regressionThreshold since the previous run.
| Option | Type | Default | Description |
|---|---|---|---|
trackRegressions | boolean | false | Persist metrics and flag run-over-run regressions |
historyDir | string | .frontguard/perf-history | Where per-run metrics are stored |
regressionThreshold | number | 0.2 | Relative increase that counts as a regression (0.2 = 20% worse) |
Tracked metrics are LCP, CLS, TTFB, and page weight (all "lower is better"). The first run establishes a baseline; subsequent runs report deltas like:
⚡ PERFORMANCE
/ @ 1440px
ttfb: 0.54s (↑ +35% since last run, was 0.40s)In the PR comment this is correlated inline with the visual diff — a page that
both shifted and got slower since the last run shows
⚡ Performance — regressed since last run: ttfb +35% (0.40s → 0.54s) right in
its details block, plus a "regressions since last run" table in the summary.
Metrics are persisted in afterRun, after results are built — so a run that
aborts midway (e.g. an accessibility failOnViolation) won't silently advance
the baseline and hide a regression on the next run.
Accessibility Audits
Run axe-core accessibility checks in the same render pass as your visual tests — contrast, alt text, target size, focus, and heading order — surfaced in every report.
Third-Party Script Monitoring
Detect when ad networks, analytics SDKs, or chat widgets appear or disappear on a page between runs — before they silently break your layout.