|$ curl https://forge-ai.dev/api/markdown?path=docs/ops/performance
$cat docs/performance-monitoring.md
updated Recently·30 min read·published

Performance Monitoring

OperationsPerformanceIntermediate
Introduction

Performance monitoring tracks how real users experience your application. It answers critical questions: Is the site fast? Which pages are slow? Did the latest deployment improve or degrade performance?

Performance directly impacts business metrics: a 1-second delay in page load reduces conversions by 7% (Amazon), and a 0.1-second improvement increases engagement by 8% (Google). Effective monitoring catches regressions before they affect users.

Core Web Vitals

Core Web Vitals are Google's standardized metrics for measuring user experience quality. They are used as ranking signals in Google Search.

MetricMeasuresGoodNeeds ImprovementPoor
LCPLoading (largest content)≤ 2.5s2.5s - 4.0s> 4.0s
INPInteractivity (response)≤ 200ms200ms - 500ms> 500ms
CLSVisual stability (layout shift)≤ 0.10.1 - 0.25> 0.25
FIDFirst input delay (legacy)≤ 100ms100ms - 300ms> 300ms
TTFBTime to first byte (server)≤ 800ms800ms - 1.8s> 1.8s

info

INP (Interaction to Next Paint) replaced FID (First Input Delay) as a Core Web Vital in March 2024. Unlike FID which only measured the first interaction, INP measures the latency of all interactions throughout a page visit. Optimize for INP by reducing JavaScript execution time and breaking long tasks.
Web Vitals Library (RUM)

The web-vitals library captures real user metrics (RUM) from your actual visitors. It reports LCP, CLS, INP, FCP, and TTFB directly from the browser.

web-vitals.ts
TypeScript
1// web-vitals — Real User Monitoring
2import { onLCP, onCLS, onINP, onFCP, onTTFB } from 'web-vitals';
3
4// Send vitals to your analytics
5function sendToAnalytics(metric) {
6 const body = JSON.stringify({
7 name: metric.name,
8 value: metric.value,
9 rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
10 delta: metric.delta,
11 id: metric.id,
12 url: location.href,
13 userAgent: navigator.userAgent,
14 connection: navigator.connection?.effectiveType,
15 deviceMemory: navigator.deviceMemory,
16 });
17
18 // Use sendBeacon for reliability (sends even during page unload)
19 if (navigator.sendBeacon) {
20 navigator.sendBeacon('/api/vitals', body);
21 } else {
22 fetch('/api/vitals', { method: 'POST', body, keepalive: true });
23 }
24}
25
26// Register metric observers
27onLCP(sendToAnalytics);
28onCLS(sendToAnalytics);
29onINP(sendToAnalytics);
30onFCP(sendToAnalytics);
31onTTFB(sendToAnalytics);
32
33// Next.js integration — in pages/_app.tsx or app/layout.tsx
34export function reportWebVitals(metric) {
35 switch (metric.name) {
36 case 'FCP':
37 case 'LCP':
38 case 'CLS':
39 case 'FID':
40 case 'TTFB':
41 case 'INP':
42 sendToAnalytics(metric);
43 break;
44 }
45}

best practice

Use navigator.sendBeacon for analytics data — it works even when the page is unloading. Store the data server-side and feed it into your analytics or monitoring platform (Google Analytics, Datadog, Grafana). Aggregate by URL to identify the slowest pages.
Lighthouse CI

Lighthouse CI runs automated audits as part of your CI/CD pipeline. It scores each page on performance, accessibility, best practices, and SEO, then compares against a baseline to detect regressions.

lighthouserc.json
YAML
1# .lighthouserc.json — Configuration
2{
3 "ci": {
4 "collect": {
5 "numberOfRuns": 3,
6 "staticDistDir": ".next/server/pages",
7 "url": [
8 "http://localhost/",
9 "http://localhost/about",
10 "http://localhost/blog"
11 ],
12 "settings": {
13 "preset": "desktop",
14 "throttlingMethod": "simulate"
15 }
16 },
17 "assert": {
18 "assertions": {
19 "categories:performance": ["error", { "minScore": 0.9 }],
20 "categories:accessibility": ["error", { "minScore": 0.95 }],
21 "categories:best-practices": ["error", { "minScore": 0.9 }],
22 "categories:seo": ["error", { "minScore": 0.95 }],
23 "lcp": ["error", { "maxNumericValue": 2500 }],
24 "cls": ["error", { "maxNumericValue": 0.1 }],
25 "total-blocking-time": ["warn", { "maxNumericValue": 200 }]
26 }
27 },
28 "upload": {
29 "target": "temporary-public-storage"
30 }
31 }
32}
33
34# GitHub Actions workflow
35name: Lighthouse CI
36on: [pull_request]
37jobs:
38 lighthouse:
39 runs-on: ubuntu-latest
40 steps:
41 - uses: actions/checkout@v4
42 - uses: actions/setup-node@v4
43 - run: npm ci && npm run build
44 - run: npx lhci autorun
45 - name: Comment PR
46 uses: marocchino/sticky-pull-request-comment@v2
47 with:
48 message: |
49 ## Lighthouse Scores
50 ${{ steps.lhci.outputs.results }}
51}
Performance Budgets

A performance budget sets limits on page size, resource counts, and loading times. Violations trigger CI failures or warnings, catching bloated bundles before they impact users.

performance-budgets.json
JSON
1// Performance budget example (via webpack or next.config.js)
2{
3 "budgets": [
4 {
5 "resourceType": "total",
6 "budget": 500 // 500 KB total page weight
7 },
8 {
9 "resourceType": "script",
10 "budget": 250 // 250 KB JavaScript
11 },
12 {
13 "resourceType": "image",
14 "budget": 150 // 150 KB images
15 },
16 {
17 "resourceType": "font",
18 "budget": 50 // 50 KB fonts
19 },
20 {
21 "resourceType": "stylesheet",
22 "budget": 30 // 30 KB CSS
23 },
24 {
25 "resourceType": "third-party",
26 "budget": 100 // 100 KB third-party scripts
27 }
28 ]
29}
30
31// next.config.js — Bundle analyzer
32const withBundleAnalyzer = require('@next/bundle-analyzer')({
33 enabled: process.env.ANALYZE === 'true',
34});
35
36module.exports = withBundleAnalyzer({
37 // ... your config
38});
🔥

pro tip

Use @next/bundle-analyzer to visualize bundle sizes and identify large dependencies. A common culprit is importing entire libraries when only a single function is needed — tree-shaking should handle ES modules, but not all libraries are tree-shakable. Tools like bundlesize or size-limit can enforce budgets in CI.
Synthetic Testing

Synthetic testing measures performance from controlled environments, complementing RUM data with consistent, repeatable measurements. Unlike RUM, synthetic tests are not affected by user device variability.

ToolTypeUse Case
Lighthouse CIAutomatedCI pipeline performance gating
WebPageTestOn-demandDeep waterfall analysis, filmstrip
SpeedCurveContinous syntheticSynthetic + RUM unified dashboard
ChecklyBrowser checksPlaywright-based multi-step checks
synthetic-testing.sh
Bash
1# Run a WebPageTest from the CLI
2npx webpagetest https://example.com \
3 --key YOUR_API_KEY \
4 --location Dulles:Chrome \
5 --firstViewOnly \
6 --video 1
7
8# Checkly browser check example (deployed as a check)
9const { BrowserCheck } = require('checkly');
10const { launch } = require('puppeteer');
11
12const browser = await launch({ headless: true });
13const page = await browser.newPage();
14
15// Set throttling condition
16await page.emulateNetworkConditions({
17 download: 5000, // 5 Mbps
18 upload: 3000, // 3 Mbps
19 latency: 40, // 40ms
20});
21
22const start = Date.now();
23await page.goto('https://example.com');
24const loadTime = Date.now() - start;
25
26// Assert performance
27if (loadTime > 3000) {
28 throw new Error(`Page load time too high: ${loadTime}ms`);
29}
30
31await browser.close();
Performance Regression Alerts

Set up automated alerts when key metrics degrade beyond thresholds. This catches performance regressions introduced by new code or infrastructure changes.

perf-alerts.ts
TypeScript
1// Performance alerting logic (server-side)
2interface PerformanceAlert {
3 metric: string;
4 threshold: number;
5 current: number;
6 page: string;
7 severity: 'warning' | 'critical';
8}
9
10async function checkPerformanceBudget(metrics: PerformanceAlert[]) {
11 const violations = metrics.filter(m => m.current > m.threshold);
12
13 for (const violation of violations) {
14 await sendSlackAlert({
15 channel: '#perf-alerts',
16 text: `${violation.severity.toUpperCase()}: ${violation.metric}
17 exceeded threshold on ${violation.page}
18 Current: ${violation.current}
19 Threshold: ${violation.threshold}`,
20 });
21
22 if (violation.severity === 'critical') {
23 await triggerPagerDuty({
24 summary: `Performance regression: ${violation.metric}`,
25 severity: 'critical',
26 });
27 }
28 }
29}
30
31// Example alert rules:
32const alertRules = {
33 LCP: { warning: 3000, critical: 4000 },
34 CLS: { warning: 0.15, critical: 0.25 },
35 INP: { warning: 300, critical: 500 },
36 TTFB: { warning: 1500, critical: 2000 },
37 JS_BYTES: { warning: 400000, critical: 500000 },
38};
$Blueprint — Engineering Documentation·Section ID: OPS-PERF-01·Revision: 1.0