Why Performance Profiling Matters
Performance profiling is the practice of recording and analyzing how your application uses CPU, memory, and rendering resources over time. Without profiling, performance optimization is guesswork. With profiling, you can pinpoint exactly which functions are slow, which layout shifts cause jank, and which components trigger unnecessary re-renders.
Key Performance Metrics
- FPS (Frames Per Second): Target 60fps for smooth animations. Each frame has 16.67ms budget.
- LCP (Largest Contentful Paint): Time until the largest visible content element renders. Target: under 2.5s.
- INP (Interaction to Next Paint): Time from user input to visual feedback. Target: under 200ms.
- CLS (Cumulative Layout Shift): Visual stability score. Target: under 0.1.
- TBT (Total Blocking Time): Total time the main thread was blocked for more than 50ms. Target: under 200ms.
Recording a Performance Profile
Open the Performance panel (Cmd + Option + I then click Performance) and follow these steps:
- Click the record button (circle icon) or press
Cmd + Eto start recording. - Perform the user interaction you want to analyze (scroll, click, type, navigate).
- Click the stop button or press
Cmd + Eagain to stop recording. - DevTools processes the data and displays the timeline.
For page load profiling, click the reload button (circular arrow) or press Cmd + Shift + E. This starts recording, reloads the page, and automatically stops after the page finishes loading.
Recording Settings
# Performance panel settings (gear icon)
# CPU Throttling: 4x slowdown or 6x slowdown to simulate slower devices
# Network Throttling: Fast 3G, Slow 3G to simulate real-world conditions
# Enable Advanced Paint Instrumentation: shows detailed paint info
# Disable JavaScript Samples: reduces overhead for long recordings
Reading the Flame Chart
The flame chart is the heart of performance profiling. It visualizes function execution over time. Each horizontal bar represents a function call. The width represents how long it took. Bars are stacked vertically to show the call stack: the bottom bar called the one above it.
Color Coding
- Yellow: JavaScript execution (scripting)
- Purple: Layout calculations (rendering)
- Green: Paint and compositing operations
- Blue: HTML parsing and style recalculations
- Gray: System / idle time
Look for wide yellow bars (long-running JavaScript) and repeated purple bars (layout thrashing). Click any bar to see its details: function name, source file, line number, self time versus total time.
Identifying Common Performance Problems
Long Tasks
Any task that blocks the main thread for more than 50ms is flagged as a "Long Task" with a red corner marker. Long tasks cause the browser to become unresponsive. Users experience this as jank during scrolling or delayed response to clicks and keypresses.
// Bad: Long synchronous loop blocking the main thread
function processLargeArray(items: string[]) {
const results = [];
for (let i = 0; i < items.length; i++) {
results.push(expensiveTransform(items[i])); // blocks for 200ms+
}
return results;
}
// Good: Break work into chunks using requestIdleCallback or setTimeout
async function processLargeArrayAsync(items: string[]) {
const results: string[] = [];
const CHUNK_SIZE = 100;
for (let i = 0; i < items.length; i += CHUNK_SIZE) {
const chunk = items.slice(i, i + CHUNK_SIZE);
chunk.forEach(item => results.push(expensiveTransform(item)));
// Yield to the main thread between chunks
await new Promise(resolve => setTimeout(resolve, 0));
}
return results;
}
Layout Thrashing
Layout thrashing occurs when JavaScript repeatedly reads and then writes to the DOM, forcing the browser to recalculate layout multiple times in a single frame. In the flame chart, this appears as repeated "Recalculate Style" and "Layout" events interleaved with scripting.
// Bad: Layout thrashing - reading then writing in a loop
const elements = document.querySelectorAll('.item');
elements.forEach(el => {
const height = el.offsetHeight; // FORCES LAYOUT (read)
el.style.height = height * 2 + 'px'; // INVALIDATES LAYOUT (write)
// Next iteration reads again, forcing another layout!
});
// Good: Batch reads, then batch writes
const elements2 = document.querySelectorAll('.item');
const heights = Array.from(elements2).map(el => el.offsetHeight); // All reads
elements2.forEach((el, i) => {
el.style.height = heights[i] * 2 + 'px'; // All writes
});
Excessive Re-renders
In React applications, unnecessary re-renders are a common performance problem. Enable React DevTools Profiler to see which components rendered during a commit and why. In the Performance panel, look for repeated React reconciliation cycles in the flame chart.
The Rendering Drawer
Open the Rendering drawer (Cmd + Shift + P then type "rendering") for visual debugging overlays:
- Paint Flashing: Highlights areas that are being repainted in green. Frequent or large green flashes indicate unnecessary repaints.
- Layout Shift Regions: Shows layout shifts with blue highlights. Useful for finding CLS sources.
- FPS Meter: Displays a real-time FPS counter overlay and GPU memory usage.
- Scrolling Performance Issues: Flags elements with non-composited scroll or touch event handlers that hurt scroll performance.
- Core Web Vitals: Shows LCP, CLS, and INP markers directly in the viewport.
Performance API in Code
// Measure specific operations with the Performance API
performance.mark('fetchStart');
const data = await fetch('/api/data');
performance.mark('fetchEnd');
performance.measure('API Fetch', 'fetchStart', 'fetchEnd');
// Read the measurement
const measure = performance.getEntriesByName('API Fetch')[0];
console.log(`API Fetch took ${measure.duration.toFixed(2)}ms`);
// Use PerformanceObserver for continuous monitoring
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'longtask') {
console.warn(`Long task detected: ${entry.duration.toFixed(2)}ms`);
}
}
});
observer.observe({ entryTypes: ['longtask', 'largest-contentful-paint'] });
// Web Vitals library for production monitoring
import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
Lighthouse Audits
The Lighthouse panel (accessible from DevTools or via CLI) runs automated audits for performance, accessibility, best practices, SEO, and PWA compliance. It provides actionable recommendations with estimated savings.
# Run Lighthouse from the command line
npx lighthouse https://example.com --view --output html
# With specific categories
npx lighthouse https://example.com \
--only-categories=performance,accessibility \
--output=json \
--output-path=./report.json
# CI-friendly with budgets
npx lighthouse https://example.com \
--budget-path=./budgets.json \
--output=json
Performance Profiling Best Practices
- Always profile on real devices: Desktop CPUs are 4-10x faster than mobile. Use CPU throttling or test on physical devices.
- Profile production builds: Development builds include extra checks, React DevTools hooks, and unminified code that heavily skew results.
- Use incognito mode: Extensions can interfere with profiling. Use incognito or a clean profile.
- Record short, focused sessions: Record only the specific interaction you are investigating. Long recordings are harder to analyze.
- Compare before and after: Save profile screenshots or export traces to compare before and after optimization.