DNAπŸš€ PerformancePerformance Monitoring, Budgets & Culture
πŸ¦–DinosaurPerformanceDevOpsArchitectureMonitoring

Performance Monitoring, Budgets & Culture

Performance isn't a one-time fix. It's a culture β€” measured in CI, monitored in production, and defended with budgets. This is how architects keep apps fast.

Performance Monitoring, Budgets & Culture

Optimizing performance once is easy. Keeping it fast across 20 developers, 50 PRs/week, and years of feature development β€” that requires systems: automated monitoring, enforced budgets, and a culture that treats performance as a feature, not an afterthought.

The Monitoring Stack

Development          CI/CD Pipeline        Production
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ DevTools      β”‚    β”‚ Lighthouse CI    β”‚  β”‚ Real User Monitoring β”‚
β”‚ Lighthouse    β”‚    β”‚ Bundle budgets   β”‚  β”‚ (RUM)                β”‚
β”‚ Performance   β”‚ β†’  β”‚ Size checks      β”‚β†’ β”‚ Error tracking       β”‚
β”‚ tab           β”‚    β”‚ Visual regressionβ”‚  β”‚ Alerting             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Lab Monitoring (Development & CI)

Lighthouse CI

# .lighthouserc.yml
ci:
  collect:
    url:
      - http://localhost:3000/
      - http://localhost:3000/products
      - http://localhost:3000/dashboard
    settings:
      preset: desktop
  assert:
    assertions:
      categories:performance:
        - error
        - minScore: 0.9
      largest-contentful-paint:
        - error
        - maxNumericValue: 2500
      cumulative-layout-shift:
        - error
        - maxNumericValue: 0.1
      total-blocking-time:
        - error
        - maxNumericValue: 300
      interactive:
        - warn
        - maxNumericValue: 3500

Bundle Size Checks

// bundlewatch.config.js
module.exports = {
  files: [
    { path: '.next/static/chunks/main-*.js', maxSize: '80kb' },
    { path: '.next/static/chunks/pages/**/*.js', maxSize: '100kb' },
    { path: '.next/static/css/**/*.css', maxSize: '30kb' },
  ],
  ci: {
    trackBranches: ['main'],
    repoBranchBase: 'main',
  },
};

Every PR shows the bundle size diff. Increases beyond the budget require justification.

Real User Monitoring (RUM)

Lab data tells you what could happen. RUM tells you what does happen β€” on real devices, real networks, for real users.

Core Implementation

import { onLCP, onINP, onCLS, onFCP, onTTFB } from 'web-vitals';
 
function sendMetric({ name, value, rating, id, navigationType }) {
  const body = {
    metric: name,
    value: Math.round(name === 'CLS' ? value * 1000 : value),
    rating,     // 'good', 'needs-improvement', 'poor'
    id,
    page: window.location.pathname,
    connection: navigator.connection?.effectiveType,
    deviceMemory: navigator.deviceMemory,
    userAgent: navigator.userAgent,
    timestamp: Date.now(),
    navigationType,
  };
 
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/api/vitals', JSON.stringify(body));
  } else {
    fetch('/api/vitals', {
      method: 'POST',
      body: JSON.stringify(body),
      keepalive: true,
    });
  }
}
 
onLCP(sendMetric);
onINP(sendMetric);
onCLS(sendMetric);
onFCP(sendMetric);
onTTFB(sendMetric);

Why sendBeacon?

navigator.sendBeacon sends data even when the page is unloading (tab close, navigation away). Regular fetch may be cancelled. For metrics that finalize at page unload (CLS, INP), sendBeacon is essential.

Segmenting RUM Data

Aggregate p75 is misleading. Segment by:

DimensionWhy
Page/routeDifferent pages have different characteristics
Device classMobile vs desktop performance differs 3-5x
Connection type4G vs 3G vs WiFi
GeographyCDN coverage varies by region
User journeyFirst visit vs return (cache effects)
BrowserSafari vs Chrome rendering differences
-- Example: LCP by device and page
SELECT
  page,
  device_class,
  PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY lcp_value) as p75_lcp,
  COUNT(*) as sample_size
FROM web_vitals
WHERE metric = 'LCP' AND timestamp > NOW() - INTERVAL '7 days'
GROUP BY page, device_class
ORDER BY p75_lcp DESC;

Performance Budgets

Budget Types

Budget TypeWhat it ControlsExample
Size budgetTransfer size of resourcesJS < 200KB, CSS < 50KB, Images < 500KB
Timing budgetUser-perceived metricsLCP < 2.5s, INP < 200ms
Count budgetNumber of requests< 50 requests, < 5 third-party scripts
Rule budgetLighthouse audit scoresPerformance > 90, Accessibility > 95

Implementing Budgets in CI

# GitHub Actions
name: Performance Budget
on: [pull_request]
 
jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
 
      - name: Lighthouse CI
        uses: treosh/lighthouse-ci-action@v11
        with:
          configPath: .lighthouserc.yml
          uploadArtifacts: true
 
  bundle-size:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
 
      - name: Check bundle size
        uses: siddharthkp/bundlewatch@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

Budget Alerts

// Alert when p75 metrics degrade
function checkBudgets(metrics) {
  const budgets = {
    LCP: 2500,
    INP: 200,
    CLS: 0.1,
    FCP: 1800,
    TTFB: 800,
  };
 
  for (const [metric, threshold] of Object.entries(budgets)) {
    const p75 = calculatePercentile(metrics[metric], 75);
    if (p75 > threshold) {
      alert({
        severity: 'warning',
        message: `${metric} p75 is ${p75}ms (budget: ${threshold}ms)`,
        page: metrics.page,
      });
    }
  }
}

Custom Performance Metrics

Beyond Web Vitals, measure what matters for your product:

// Time to first meaningful interaction (custom)
performance.mark('app-interactive');
performance.measure('time-to-interactive', 'navigationStart', 'app-interactive');
 
// Feature-specific metrics
function trackFeatureLoad(featureName) {
  performance.mark(`${featureName}-start`);
  return () => {
    performance.mark(`${featureName}-end`);
    const measure = performance.measure(
      `${featureName}-load`,
      `${featureName}-start`,
      `${featureName}-end`
    );
    sendMetric({ name: featureName, value: measure.duration });
  };
}
 
const endTrack = trackFeatureLoad('dashboard-charts');
await loadCharts();
endTrack(); // Reports: "dashboard-charts: 450ms"

Server Timing

Server-Timing: db;dur=53, cache;desc="Cache Read";dur=2, render;dur=47
const [navigation] = performance.getEntriesByType('navigation');
navigation.serverTiming.forEach(({ name, duration, description }) => {
  console.log(`${name}: ${duration}ms (${description})`);
});

Server-Timing headers let you surface server-side performance data in browser DevTools and RUM.

The Performance Culture

Make Performance Visible

  1. Dashboard β€” Real-time Web Vitals displayed in the office / Slack
  2. PR comments β€” Bot posts bundle size diff and Lighthouse scores on every PR
  3. Weekly reports β€” p75 trends by route, regression detection
  4. Incident process β€” Performance regressions treated with the same urgency as bugs

The Performance Review Checklist

Every PR that touches UI should consider:

## Performance Review
- [ ] No new synchronous blocking resources in <head>
- [ ] Images have explicit width/height
- [ ] New features are lazy-loaded if not above-fold
- [ ] No new dependencies > 10KB gzipped without justification
- [ ] Event handlers use passive where appropriate
- [ ] Bundle size change is within budget

Ownership

Assign a "performance champion" per team:

  • Reviews performance implications of PRs
  • Monitors RUM dashboards
  • Investigates regressions within 24 hours
  • Maintains budgets and updates them quarterly

Interview Signal

Senior candidates demonstrate:

  1. End-to-end thinking β€” Dev tools β†’ CI gates β†’ production monitoring β†’ alerting
  2. RUM sophistication β€” Segmentation, p75 targeting, sendBeacon for reliable collection
  3. Budget enforcement β€” Not just setting budgets but integrating them into CI/CD
  4. Custom metrics β€” Measuring what matters for the specific product, not just generic vitals
  5. Culture perspective β€” Performance as ongoing discipline, not a sprint task