← Catalog

No. 155 · performance

Performance Profiling

Find bottlenecks before users do

Version 1.0.0 License MIT Format SKILL.md

You can’t optimize what you can’t measure. Profiling turns “it feels slow” into “this specific function takes 340ms on the main thread.”

Chrome DevTools performance audit

## Recording a performance profile
1. Open DevTools → Performance tab
2. Click Record, interact with the page, Stop
3. Focus on: Long tasks (>50ms), Layout shifts, Scripting time

## Key metrics to examine
- **FPS**: Should be 60fps consistently
- **CPU**: Main thread shouldn't be >80% utilized
- **Memory**: Look for growing heap over time (leak)

## Common bottlenecks
- Excessive DOM size (>1500 nodes)
- Forced synchronous layouts (reading layout properties after writing)
- Long-running JavaScript blocking the main thread
- Unoptimized images (no lazy loading, oversized)

Node.js profiling with clinic.js

# Doctor: automated diagnosis
npx clinic doctor -- node server.js
# Interact with app, then Ctrl+C
# Generates HTML report with recommendations

# Flame: CPU profiling
npx clinic flame -- node server.js
# Shows which functions consume the most CPU

# Bubbleprof: async profiling
npx clinic bubbleprof -- node server.js
# Shows async operations and event loop lag

# Heap: memory profiling
npx clinic heap -- node server.js
# Identifies memory leaks and high allocation

Performance budgets

{
  "budgets": [
    {
      "type": "initial",
      "maximumWarning": "200kb",
      "maximumError": "300kb"
    },
    {
      "type": "bundle",
      "name": "vendor",
      "maximumWarning": "100kb",
      "maximumError": "150kb"
    }
  ],
  "thresholds": {
    "first-contentful-paint": "1.5s",
    "largest-contentful-paint": "2.5s",
    "cumulative-layout-shift": "0.1",
    "total-blocking-time": "200ms",
    "time-to-interactive": "3.5s"
  }
}

Memory leak detection

// React: detect component memory leaks
useEffect(() => {
  const controller = new AbortController();

  async function fetchData() {
    const res = await fetch('/api/data', {
      signal: controller.signal,
    });
    const data = await res.json();
    setData(data);
  }

  fetchData();

  // Cleanup on unmount
  return () => controller.abort();
}, []);

// Node.js: monitor heap usage
setInterval(() => {
  const mem = process.memoryUsage();
  console.log({
    rss: `${Math.round(mem.rss / 1024 / 1024)}MB`,
    heap: `${Math.round(mem.heapUsed / 1024 / 1024)}MB`,
    external: `${Math.round(mem.external / 1024 / 1024)}MB`,
  });
}, 30000);

Web Vitals monitoring

import { onCLS, onFCP, onLCP, onTTFB, onINP } from 'web-vitals';

function sendToAnalytics(metric: Metric) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
    delta: metric.delta,
    id: metric.id,
    navigationType: metric.navigationType,
  });

  // Use sendBeacon for non-blocking analytics
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/analytics', body);
  } else {
    fetch('/analytics', { body, method: 'POST', keepalive: true });
  }
}

onCLS(sendToAnalytics);
onFCP(sendToAnalytics);
onLCP(sendToAnalytics);
onTTFB(sendToAnalytics);
onINP(sendToAnalytics);

Profiling checklist

## CPU
- [ ] Record performance profile in DevTools
- [ ] Identify long tasks (>50ms)
- [ ] Check for forced synchronous layouts
- [ ] Profile server-side rendering time
- [ ] Measure Time to Interactive (TTI)

## Memory
- [ ] Monitor heap size over time
- [ ] Take heap snapshots at different points
- [ ] Compare snapshots to find leaked objects
- [ ] Check for detached DOM nodes
- [ ] Verify event listener cleanup

## Network
- [ ] Measure First Byte Time (TTFB < 200ms)
- [ ] Check for render-blocking resources
- [ ] Verify resource compression (gzip/brotli)
- [ ] Audit image sizes and formats
- [ ] Check cache hit rates

## Rendering
- [ ] Measure First Contentful Paint (FCP < 1.8s)
- [ ] Check Largest Contentful Paint (LCP < 2.5s)
- [ ] Monitor Cumulative Layout Shift (CLS < 0.1)
- [ ] Verify no layout thrashing

Anti-patterns

  • Don’t optimize without measuring — profile first
  • Don’t ignore the main thread — JavaScript blocks rendering
  • Don’t skip cleanup — event listeners and timers cause memory leaks
  • Don’t trust synthetic benchmarks — measure real user performance
  • Don’t optimize images without measuring — check if they’re actually slow

When it triggers

  • profiling application performance
  • finding performance bottlenecks
  • memory leak detection
  • Chrome DevTools performance
  • Node.js profiling