. Initiate Capture: Switch to the Performance tab. Click the record icon, reload the page, and stop recording after the load event.
3. Analyze Flame Chart:
* Focus on the Main thread.
* Identify large blocks in Purple (Layout) or Orange (Paint). These indicate rendering bottlenecks.
* A healthy profile shows the majority of activity in Blue (Loading) and Yellow (Scripting) completing within the first 2 seconds.
4. Verify FCP: Right-click the summary panel and enable "Show summary". Check First Contentful Paint (FCP). Target: < 1.8s.
Architecture Decision: Always audit with "Disable Cache" enabled in the Network tab during initial analysis. This ensures you are measuring the cost of transfer and parsing, not just cache hits.
Phase 2: Transfer Efficiency & Dead Code Elimination
Objective: Quantify and remove unused assets to reduce transfer size.
- Open Coverage: Navigate to More Tools > Coverage.
- Instrument: Click "Start instrumenting coverage and reload page".
- Filter Results:
- Sort by Unused Bytes descending.
- Filter for files with >30% unused ratio.
- Remediation Strategy:
- CSS: If a stylesheet is unused on the current route, implement route-based code splitting or critical CSS extraction.
- JS: Verify if the code is tree-shakeable. If not, refactor imports to use named exports rather than namespace imports.
Production Tip: Coverage reports per-page usage. For Single Page Applications (SPAs), aggregate coverage data across multiple routes before deleting code to avoid breaking navigation.
Phase 3: Critical Resource Optimization
Objective: Eliminate render-blocking resources that delay FCP.
- Waterfall Analysis: In the Network tab, sort by Waterfall.
- Identify Blockers: Look for resources colored Purple or those with long "Waiting for Server Response" times before the
</head> tag closes.
- Fix Patterns:
- Non-Critical CSS: Defer loading using a media query swap pattern.
- Critical Fonts: Preload essential font files to prevent FOIT (Flash of Invisible Text).
Implementation Example: Non-Critical CSS Injection
Instead of blocking the render path, inject non-critical styles after the initial paint using requestIdleCallback.
// utils/performance.ts
export const deferNonCriticalStyles = (selector: string = 'link[rel="preload"][as="style"]') => {
const loadDeferredStyles = () => {
const links = document.querySelectorAll<HTMLLinkElement>(selector);
links.forEach(link => {
if (link.getAttribute('as') === 'style') {
link.rel = 'stylesheet';
}
});
};
if ('requestIdleCallback' in window) {
requestIdleCallback(loadDeferredStyles);
} else {
window.addEventListener('load', loadDeferredStyles);
}
};
// Usage in application entry point
deferNonCriticalStyles();
Phase 4: Third-Party Script Isolation
Objective: Prevent analytics, ads, and widgets from blocking the main thread.
- Size Audit: In the Network tab, sort by Size descending.
- Identify Offenders: Scripts from analytics, chat widgets, or ad networks often appear in the top transfers.
- Remediation:
- Apply
async attribute to scripts that do not depend on DOM readiness.
- Implement lazy loading based on user interaction or visibility.
Implementation Example: Visibility-Based Lazy Loading
Use IntersectionObserver to load third-party scripts only when they enter the viewport, rather than arbitrary timeouts.
// utils/lazy-script.ts
interface LazyScriptConfig {
triggerSelector: string;
srcAttribute: string;
}
export const initLazyScripts = ({ triggerSelector, srcAttribute }: LazyScriptConfig) => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const trigger = entry.target as HTMLElement;
const scriptSrc = trigger.getAttribute(srcAttribute);
if (scriptSrc) {
const script = document.createElement('script');
script.src = scriptSrc;
script.async = true;
document.body.appendChild(script);
}
observer.unobserve(trigger);
}
});
}, { rootMargin: '200px' });
document.querySelectorAll(triggerSelector).forEach(el => observer.observe(el));
};
// Usage: <div class="lazy-trigger" data-src="https://analytics.com/script.js"></div>
initLazyScripts({ triggerSelector: '.lazy-trigger', srcAttribute: 'data-src' });
Phase 5: Core Web Vitals Verification
Objective: Validate LCP and other vitals programmatically.
- Console Sentinel: Run a performance observer to log LCP values with rating thresholds.
- Target: LCP must be
< 2500ms.
Implementation Example: CWV Sentinel Script
This script provides immediate feedback on LCP with rating classification.
// utils/cwv-sentinel.ts
export const monitorLCP = () => {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
const value = lastEntry.startTime;
const rating = value <= 2500 ? 'GOOD' : value <= 4000 ? 'NEEDS IMPROVEMENT' : 'POOR';
console.log(`[CWV] LCP: ${Math.round(value)}ms [${rating}]`);
// Optional: Send to analytics
// analytics.track('LCP', { value, rating });
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
};
monitorLCP();
Pitfall Guide
| Pitfall Name | Explanation | Fix |
|---|
| The Localhost Illusion | Testing on localhost or fast Wi-Fi hides network latency and parsing costs. | Always enable "Slow 3G" throttling and "Disable Cache" during audits. |
| Async Misuse | Applying async to scripts required for initial rendering breaks functionality. | Only use async for scripts that do not modify the DOM or block FCP. Use defer for scripts needed at DOMContentLoaded. |
| Coverage False Positives | Deleting code marked "unused" in Coverage because it's not used on the current page, breaking other routes. | Aggregate coverage data across all routes or use build-time tree-shaking analysis before deletion. |
| DOM Node Explosion | Frameworks like React/Vue can generate excessive wrapper divs, increasing layout time. | Audit DOM node count. Target < 1500 nodes. Use Fragments and remove unnecessary wrappers. |
| Font Display FOIT | Fonts block text rendering, causing invisible text during load. | Add font-display: swap to @font-face rules to show fallback text immediately. |
| Image Format Neglect | Serving JPEG/PNG instead of modern formats increases transfer size unnecessarily. | Convert images to WebP or AVIF. Use srcset for responsive delivery. |
| Main Thread Saturation | Long tasks (>50ms) block user input, hurting INP. | Break up long tasks using scheduler.postTask or setTimeout chunking. |
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Marketing Landing Page | Aggressive CSS/JS pruning + Image optimization | Single page, high traffic volume, simple structure | Low dev cost, high ROI |
| Complex SPA | Route-based code splitting + Async third-party | User journey varies; loading all code upfront is wasteful | Medium dev cost, scalable |
| E-commerce Product | Preload critical assets + Lazy load below-fold | Visual heavy; LCP is critical for conversion | Low dev cost, direct revenue impact |
| Legacy Monolith | Third-party isolation + Font optimization | Quick wins without refactoring architecture | Low dev cost, immediate relief |
Configuration Template
Use this package.json configuration to automate performance regression testing in your CI pipeline. This prevents performance debt from accumulating.
{
"scripts": {
"audit:local": "lighthouse http://localhost:3000 --view --output=json --output-path=./reports/perf-report.json --only-categories=performance",
"audit:ci": "lighthouse http://localhost:3000 --output=json --only-categories=performance --score-thresholds=performance:90",
"audit:compare": "node ./scripts/compare-reports.js ./reports/prev.json ./reports/curr.json"
},
"devDependencies": {
"lighthouse": "^11.0.0",
"lhci": "^0.13.0"
}
}
Quick Start Guide
- Open DevTools: Press
F12 or Ctrl+Shift+I.
- Configure Throttling: In the Network tab, select "Slow 3G" and check "Disable Cache".
- Run Performance Audit: Go to the Performance tab, click Record, reload the page, and stop after load.
- Check Coverage: Switch to the Coverage tab, start instrumenting, and review unused bytes.
- Validate Vitals: Paste the CWV Sentinel script into the Console and verify LCP rating.
Note: Run this audit weekly during development. Performance regression often creeps in through new dependencies or growing CSS bundles. A disciplined 5-minute triage catches 80% of common speed issues before they impact production rankings.