Performance
Performance Monitoring
Metrics collection, performance budgets, and monitoring tools
Performance Monitoring
Monitoring is essential for maintaining performance over time. This covers metrics collection, tools, and establishing performance budgets.
Core Web Vitals Measurement
Using web-vitals Library
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals';
function sendToAnalytics(metric: Metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
id: metric.id,
navigationType: metric.navigationType,
});
// Use sendBeacon for reliability
if (navigator.sendBeacon) {
navigator.sendBeacon('/analytics', body);
} else {
fetch('/analytics', { body, method: 'POST', keepalive: true });
}
}
// Measure all Core Web Vitals
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);Performance Observer API
// Long Tasks (blocking main thread)
const longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Long task detected:', {
duration: entry.duration,
startTime: entry.startTime,
name: entry.name,
});
}
});
longTaskObserver.observe({ entryTypes: ['longtask'] });
// Resource Timing
const resourceObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'resource') {
console.log('Resource loaded:', {
name: entry.name,
duration: entry.duration,
transferSize: (entry as PerformanceResourceTiming).transferSize,
});
}
}
});
resourceObserver.observe({ entryTypes: ['resource'] });
// Navigation Timing
const navObserver = new PerformanceObserver((list) => {
const entry = list.getEntries()[0] as PerformanceNavigationTiming;
console.log('Navigation timing:', {
domContentLoaded: entry.domContentLoadedEventEnd - entry.startTime,
loadComplete: entry.loadEventEnd - entry.startTime,
ttfb: entry.responseStart - entry.requestStart,
});
});
navObserver.observe({ entryTypes: ['navigation'] });Performance Budgets
Configuration
// budget.json
{
"resourceSizes": [
{
"resourceType": "script",
"budget": 300
},
{
"resourceType": "stylesheet",
"budget": 50
},
{
"resourceType": "image",
"budget": 500
},
{
"resourceType": "total",
"budget": 1000
}
],
"resourceCounts": [
{
"resourceType": "script",
"budget": 15
},
{
"resourceType": "third-party",
"budget": 10
}
],
"timings": [
{
"metric": "first-contentful-paint",
"budget": 1500
},
{
"metric": "largest-contentful-paint",
"budget": 2500
},
{
"metric": "interactive",
"budget": 3500
},
{
"metric": "total-blocking-time",
"budget": 300
}
]
}Lighthouse CI
# lighthouserc.js
module.exports = {
ci: {
collect: {
url: ['http://localhost:3000/', 'http://localhost:3000/about'],
numberOfRuns: 3,
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.9 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: 1500 }],
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'total-blocking-time': ['error', { maxNumericValue: 300 }],
},
},
upload: {
target: 'temporary-public-storage',
},
},
};CI Integration
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install and Build
run: |
npm ci
npm run build
- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v10
with:
configPath: ./lighthouserc.js
uploadArtifacts: true
temporaryPublicStorage: trueReal User Monitoring (RUM)
Custom RUM Implementation
class PerformanceMonitor {
private metrics: Record<string, number> = {};
private marks: Record<string, number> = {};
mark(name: string) {
this.marks[name] = performance.now();
performance.mark(name);
}
measure(name: string, startMark: string, endMark?: string) {
const start = this.marks[startMark];
const end = endMark ? this.marks[endMark] : performance.now();
if (start !== undefined) {
this.metrics[name] = end - start;
performance.measure(name, startMark, endMark);
}
}
trackInteraction(name: string, fn: () => void | Promise<void>) {
const start = performance.now();
const result = fn();
if (result instanceof Promise) {
result.finally(() => {
this.metrics[`interaction:${name}`] = performance.now() - start;
this.report();
});
} else {
this.metrics[`interaction:${name}`] = performance.now() - start;
this.report();
}
}
private report() {
if (Object.keys(this.metrics).length > 0) {
navigator.sendBeacon('/api/metrics', JSON.stringify({
metrics: this.metrics,
url: window.location.href,
timestamp: Date.now(),
userAgent: navigator.userAgent,
}));
}
}
}
// Usage
const monitor = new PerformanceMonitor();
// Track component render
monitor.mark('component:start');
// ... render logic
monitor.measure('component:render', 'component:start');
// Track user interaction
monitor.trackInteraction('submit-form', async () => {
await submitForm(data);
});Third-Party RUM Services
// Sentry Performance
import * as Sentry from '@sentry/react';
Sentry.init({
dsn: 'YOUR_DSN',
integrations: [
new Sentry.BrowserTracing({
tracePropagationTargets: ['localhost', /^https:\/\/api\.example\.com/],
}),
],
tracesSampleRate: 0.1, // 10% of transactions
});
// Custom transaction
const transaction = Sentry.startTransaction({
name: 'User Checkout',
op: 'checkout',
});
// Add spans
const span = transaction.startChild({
op: 'http',
description: 'Fetch cart items',
});
await fetchCartItems();
span.finish();
transaction.finish();Development Tools
Chrome DevTools Performance Panel
// Programmatic performance marks for DevTools
performance.mark('app:init:start');
initializeApp();
performance.mark('app:init:end');
performance.measure('App Initialization', 'app:init:start', 'app:init:end');
// Console timing
console.time('expensive-operation');
expensiveOperation();
console.timeEnd('expensive-operation');
// Memory profiling
console.memory; // Chrome onlyReact DevTools Profiler
import { Profiler, ProfilerOnRenderCallback } from 'react';
const onRenderCallback: ProfilerOnRenderCallback = (
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) => {
console.log({
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
});
};
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<MainContent />
</Profiler>
);
}Error and Performance Correlation
// Track errors with performance context
window.addEventListener('error', (event) => {
const performanceData = {
memory: (performance as any).memory,
timing: performance.timing,
longTasks: getLongTaskCount(),
};
reportError({
message: event.message,
filename: event.filename,
lineno: event.lineno,
performance: performanceData,
});
});
// Track slow network requests
const originalFetch = window.fetch;
window.fetch = async (...args) => {
const start = performance.now();
const url = typeof args[0] === 'string' ? args[0] : args[0].url;
try {
const response = await originalFetch(...args);
const duration = performance.now() - start;
if (duration > 3000) {
reportSlowRequest({ url, duration });
}
return response;
} catch (error) {
const duration = performance.now() - start;
reportFailedRequest({ url, duration, error });
throw error;
}
};Alerting and Dashboards
Performance Alerts
// Server-side alert configuration
const alertRules = {
lcp: {
threshold: 2500,
percentile: 75,
window: '1h',
action: 'slack',
},
cls: {
threshold: 0.1,
percentile: 75,
window: '1h',
action: 'email',
},
errorRate: {
threshold: 0.01, // 1%
window: '15m',
action: 'pagerduty',
},
};
// Check and alert
async function checkAlerts(metrics: MetricData[]) {
for (const [metric, rule] of Object.entries(alertRules)) {
const value = calculatePercentile(
metrics.filter(m => m.name === metric),
rule.percentile
);
if (value > rule.threshold) {
await sendAlert(rule.action, {
metric,
value,
threshold: rule.threshold,
});
}
}
}Best Practices
Monitoring Guidelines
- Measure Core Web Vitals for all pages
- Set performance budgets and enforce in CI
- Use RUM to understand real user experience
- Correlate performance with business metrics
- Set up alerts for performance regressions
- Track performance trends over time
- Segment data by device, network, geography
- Review performance weekly with stakeholders