Performance Monitoring
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 are Google's standardized metrics for measuring user experience quality. They are used as ranking signals in Google Search.
| Metric | Measures | Good | Needs Improvement | Poor |
|---|---|---|---|---|
| LCP | Loading (largest content) | ≤ 2.5s | 2.5s - 4.0s | > 4.0s |
| INP | Interactivity (response) | ≤ 200ms | 200ms - 500ms | > 500ms |
| CLS | Visual stability (layout shift) | ≤ 0.1 | 0.1 - 0.25 | > 0.25 |
| FID | First input delay (legacy) | ≤ 100ms | 100ms - 300ms | > 300ms |
| TTFB | Time to first byte (server) | ≤ 800ms | 800ms - 1.8s | > 1.8s |
info
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.
| 1 | // web-vitals — Real User Monitoring |
| 2 | import { onLCP, onCLS, onINP, onFCP, onTTFB } from 'web-vitals'; |
| 3 | |
| 4 | // Send vitals to your analytics |
| 5 | function 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 |
| 27 | onLCP(sendToAnalytics); |
| 28 | onCLS(sendToAnalytics); |
| 29 | onINP(sendToAnalytics); |
| 30 | onFCP(sendToAnalytics); |
| 31 | onTTFB(sendToAnalytics); |
| 32 | |
| 33 | // Next.js integration — in pages/_app.tsx or app/layout.tsx |
| 34 | export 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
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.
| 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 |
| 35 | name: Lighthouse CI |
| 36 | on: [pull_request] |
| 37 | jobs: |
| 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 | } |
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.
| 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 |
| 32 | const withBundleAnalyzer = require('@next/bundle-analyzer')({ |
| 33 | enabled: process.env.ANALYZE === 'true', |
| 34 | }); |
| 35 | |
| 36 | module.exports = withBundleAnalyzer({ |
| 37 | // ... your config |
| 38 | }); |
pro tip
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.
| Tool | Type | Use Case |
|---|---|---|
| Lighthouse CI | Automated | CI pipeline performance gating |
| WebPageTest | On-demand | Deep waterfall analysis, filmstrip |
| SpeedCurve | Continous synthetic | Synthetic + RUM unified dashboard |
| Checkly | Browser checks | Playwright-based multi-step checks |
| 1 | # Run a WebPageTest from the CLI |
| 2 | npx 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) |
| 9 | const { BrowserCheck } = require('checkly'); |
| 10 | const { launch } = require('puppeteer'); |
| 11 | |
| 12 | const browser = await launch({ headless: true }); |
| 13 | const page = await browser.newPage(); |
| 14 | |
| 15 | // Set throttling condition |
| 16 | await page.emulateNetworkConditions({ |
| 17 | download: 5000, // 5 Mbps |
| 18 | upload: 3000, // 3 Mbps |
| 19 | latency: 40, // 40ms |
| 20 | }); |
| 21 | |
| 22 | const start = Date.now(); |
| 23 | await page.goto('https://example.com'); |
| 24 | const loadTime = Date.now() - start; |
| 25 | |
| 26 | // Assert performance |
| 27 | if (loadTime > 3000) { |
| 28 | throw new Error(`Page load time too high: ${loadTime}ms`); |
| 29 | } |
| 30 | |
| 31 | await browser.close(); |
Set up automated alerts when key metrics degrade beyond thresholds. This catches performance regressions introduced by new code or infrastructure changes.
| 1 | // Performance alerting logic (server-side) |
| 2 | interface PerformanceAlert { |
| 3 | metric: string; |
| 4 | threshold: number; |
| 5 | current: number; |
| 6 | page: string; |
| 7 | severity: 'warning' | 'critical'; |
| 8 | } |
| 9 | |
| 10 | async 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: |
| 32 | const 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 | }; |