Investigate Web Performance Issues
Disciplined, evidence-driven investigation of web app performance problems using chrome-devtools-mcp. This skill is the canonical loop; references are loaded on demand based on the scenario and framework.
Prerequisite: chrome-devtools-mcp and the Chrome DevTools Skills plugin must be installed. If they aren't, stop and run the setup-perf-stack skill first.
The Loop
- Reproduce deterministically. Pin emulation. Never investigate a "sometimes slow" page without first making it reliably slow.
- Capture. Record a trace; save to disk, do not stream JSON.
- Analyze via insights, never raw JSON. Call
performance_analyze_insight with a name from the fixed list of 18.
- Pin root cause to file:line. Use
evaluate_script, list_network_requests, list_console_messages, and React DevTools.
- State a single-variable hypothesis. One change per iteration.
- Fix. Make the minimum change the hypothesis predicts.
- Validate. Re-trace under identical emulation. Accept only if the target insight moves ≥10% and no other Core Web Vital regresses. Otherwise revert and re-hypothesize.
Always-Pinned Emulation Profile
Every trace in this skill uses these settings. Do not change them mid-investigation; you lose comparability.
cpuThrottlingRate: 4
networkConditions: "Slow 4G"
- Mobile viewport (390×844) unless the user specifies desktop
- Cache-cold loads via
new_page + navigate_page, not warm reloads
- Run N=3 and report the median; single runs vary ±15–30%
Tighten only if the user explicitly wants a desktop-class target (e.g., cpuThrottlingRate: 1, networkConditions: "Fast 4G"). Document the deviation in the report.
The 18-Insight Vocabulary
Pass one of these as insightName to performance_analyze_insight. Do not invent names.
Load / paint / rendering:
DocumentLatency, LCPBreakdown, LCPDiscovery, RenderBlocking
CLSCulprits, ForcedReflow, SlowCSSSelector
Interaction:
INPBreakdown, LongCriticalNetworkTree, NetworkDependencyTree
JavaScript / delivery:
DuplicatedJavaScript, LegacyJavaScript, ModernHTTP
Resources / configuration:
ThirdParties, FontDisplay, ImageDelivery, Viewport, Cache
Non-Negotiables
- Always call
performance_analyze_insight before writing any custom Perf-API JS. The 18 insights are the complete vocabulary for trace analysis. Custom performance.getEntries*() calls are a last resort, not a first move.
- Never pass raw trace JSON to the model. Save with
filePath: "trace.json.gz" and reference the path. Let the MCP's PerformanceTraceFormatter do the compression (30 MB → ~4 kB, 48 lines).
- One variable per iteration. Never ship two simultaneous changes and call them a fix — you cannot attribute the delta.
- Re-trace under identical emulation before claiming a fix. Before/after paths go in the report. No "it feels faster".
- Reject fixes <10% on the target insight, or any regression elsewhere. Revert and form a new hypothesis.
- Spawn a subagent per insight drill-down when traces are large. Each subagent returns <5 kB to the main thread. This is the single most effective way to keep the main context clean across a multi-step investigation.
- Prefer accessibility-tree snapshots over screenshots. Screenshots are ~2k tokens each and rarely carry diagnostic signal for perf work. Reserve them for CLS visual verification and canvas/WebGL issues.
Which Reference to Load
Load on demand — do not read these speculatively. The main SKILL.md (this file) already covers the universal discipline.
By scenario
| Symptom |
Load |
Slow first paint, high LCP, LCPBreakdown |
references/lcp.md |
Sluggish click/tap/type, high INP, INPBreakdown |
references/inp.md |
| UI janky on scroll/update, React components rendering too often |
references/rerenders.md + references/react.md |
| Initial JS payload too large, route-level bundle regression |
references/bundle.md |
| Heap grows unboundedly, tab slows after N minutes, detached DOM |
references/memory.md |
| Layout shift, CLS > 0.1 |
Use CLSCulprits insight directly; escalate to references/lcp.md if layout and LCP are entangled |
| Forced synchronous layout / layout thrashing |
Use ForcedReflow insight directly; load references/react.md if React is causing layout reads in render |
By framework (load in addition to the scenario reference)
| Project |
Load |
| Any React project |
references/react.md |
| Next.js 15 or 16+ |
references/nextjs.md (plus react.md) |
Do not load framework references if the project doesn't use that framework.
Loop Anti-Patterns
Watch for these and correct immediately:
- Skipping insights for custom JS. If the first tool call is
evaluate_script with a performance.* API, the agent is doing it wrong. Back up and pick an insight.
- "Let me also try..." Second change before validating the first. Revert.
- Comparing traces with different emulation. The numbers are not commensurable.
- Reporting LCP improved without checking INP/CLS. A "fix" that trades one vital for another is not a fix.
- Talking about the trace without reading the insight output. If the agent is speculating based on code rather than trace evidence, something went wrong in step 2 or 3 — re-capture.
When to Escalate to the User
- Non-determinism persists after N=3 medianing — likely real-user variance (CrUX field injection, thermal throttling, GC). Surface to the user and propose measuring in Sentry/RUM if available.
- Fix requires architectural change (e.g., switching rendering mode, moving to a CDN, adopting streaming SSR). State the hypothesis, estimated impact, and stop. Do not implement silently.
- Dependency upgrade required — confirm with the user before touching
package.json.
Reference Index
references/lcp.md — LCP regression workflow: subpart model, insight sequence, prompt template.
references/inp.md — INP / slow interaction workflow: Long Animation Frames, presentation split.
references/rerenders.md — Unnecessary re-renders workflow: react-scan, classification, React DevTools integration.
references/bundle.md — Bundle bloat workflow: DuplicatedJavaScript/LegacyJavaScript, network filtering, source mapping.
references/memory.md — Memory leak workflow: heap snapshot comparison, memlab, common leak shapes.
references/react.md — React-specific rules: memo discipline, useDeferredValue, <Profiler>, RSC streaming gotchas, tool recommendations.
references/nextjs.md — Next.js-specific rules: Turbopack tracing, instrumentation.ts, RSC payload debugging, next build output parsing.
1---2name: investigate-web-perf3description: Trace-based triage of web performance issues via chrome-devtools-mcp: LCP/INP regressions, slow page loads and interactions, ForcedReflow/layout thrashing, CLS, bundle bloat, memory leaks. Captures Chrome performance traces and analyzes them with performance insights. Requires setup-perf-stack. For installing react-scan, component-level React render attribution (unnecessary re-renders, unstable props), or automated render checks with Playwright, use react-scan instead.4---56# Investigate Web Performance Issues78Disciplined, evidence-driven investigation of web app performance problems using `chrome-devtools-mcp`. This skill is the canonical loop; references are loaded on demand based on the scenario and framework.910**Prerequisite:** `chrome-devtools-mcp` and the Chrome DevTools Skills plugin must be installed. If they aren't, stop and run the `setup-perf-stack` skill first.1112## The Loop13141. **Reproduce deterministically.** Pin emulation. Never investigate a "sometimes slow" page without first making it reliably slow.152. **Capture.** Record a trace; save to disk, do not stream JSON.163. **Analyze via insights, never raw JSON.** Call `performance_analyze_insight` with a name from the fixed list of 18.174. **Pin root cause to file:line.** Use `evaluate_script`, `list_network_requests`, `list_console_messages`, and React DevTools.185. **State a single-variable hypothesis.** One change per iteration.196. **Fix.** Make the minimum change the hypothesis predicts.207. **Validate.** Re-trace under *identical* emulation. Accept only if the target insight moves ≥10% and no other Core Web Vital regresses. Otherwise revert and re-hypothesize.2122## Always-Pinned Emulation Profile2324Every trace in this skill uses these settings. Do not change them mid-investigation; you lose comparability.2526- `cpuThrottlingRate: 4`27- `networkConditions: "Slow 4G"`28- Mobile viewport (390×844) unless the user specifies desktop29- Cache-cold loads via `new_page` + `navigate_page`, not warm reloads30- Run **N=3 and report the median**; single runs vary ±15–30%3132Tighten only if the user explicitly wants a desktop-class target (e.g., `cpuThrottlingRate: 1`, `networkConditions: "Fast 4G"`). Document the deviation in the report.3334## The 18-Insight Vocabulary3536Pass one of these as `insightName` to `performance_analyze_insight`. Do not invent names.3738Load / paint / rendering:39- `DocumentLatency`, `LCPBreakdown`, `LCPDiscovery`, `RenderBlocking`40- `CLSCulprits`, `ForcedReflow`, `SlowCSSSelector`4142Interaction:43- `INPBreakdown`, `LongCriticalNetworkTree`, `NetworkDependencyTree`4445JavaScript / delivery:46- `DuplicatedJavaScript`, `LegacyJavaScript`, `ModernHTTP`4748Resources / configuration:49- `ThirdParties`, `FontDisplay`, `ImageDelivery`, `Viewport`, `Cache`5051## Non-Negotiables52531. **Always call `performance_analyze_insight` before writing any custom Perf-API JS.** The 18 insights are the complete vocabulary for trace analysis. Custom `performance.getEntries*()` calls are a last resort, not a first move.542. **Never pass raw trace JSON to the model.** Save with `filePath: "trace.json.gz"` and reference the path. Let the MCP's `PerformanceTraceFormatter` do the compression (30 MB → ~4 kB, 48 lines).553. **One variable per iteration.** Never ship two simultaneous changes and call them a fix — you cannot attribute the delta.564. **Re-trace under identical emulation before claiming a fix.** Before/after paths go in the report. No "it feels faster".575. **Reject fixes <10% on the target insight, or any regression elsewhere.** Revert and form a new hypothesis.586. **Spawn a subagent per insight drill-down** when traces are large. Each subagent returns <5 kB to the main thread. This is the single most effective way to keep the main context clean across a multi-step investigation.597. **Prefer accessibility-tree snapshots over screenshots.** Screenshots are ~2k tokens each and rarely carry diagnostic signal for perf work. Reserve them for CLS visual verification and canvas/WebGL issues.6061## Which Reference to Load6263Load on demand — do not read these speculatively. The main SKILL.md (this file) already covers the universal discipline.6465### By scenario6667| Symptom | Load |68|---------|------|69| Slow first paint, high LCP, `LCPBreakdown` | `references/lcp.md` |70| Sluggish click/tap/type, high INP, `INPBreakdown` | `references/inp.md` |71| UI janky on scroll/update, React components rendering too often | `references/rerenders.md` + `references/react.md` |72| Initial JS payload too large, route-level bundle regression | `references/bundle.md` |73| Heap grows unboundedly, tab slows after N minutes, detached DOM | `references/memory.md` |74| Layout shift, CLS > 0.1 | Use `CLSCulprits` insight directly; escalate to `references/lcp.md` if layout and LCP are entangled |75| Forced synchronous layout / layout thrashing | Use `ForcedReflow` insight directly; load `references/react.md` if React is causing layout reads in render |7677### By framework (load *in addition* to the scenario reference)7879| Project | Load |80|---------|------|81| Any React project | `references/react.md` |82| Next.js 15 or 16+ | `references/nextjs.md` (plus `react.md`) |8384Do not load framework references if the project doesn't use that framework.8586## Loop Anti-Patterns8788Watch for these and correct immediately:8990- **Skipping insights for custom JS.** If the first tool call is `evaluate_script` with a `performance.*` API, the agent is doing it wrong. Back up and pick an insight.91- **"Let me also try..."** Second change before validating the first. Revert.92- **Comparing traces with different emulation.** The numbers are not commensurable.93- **Reporting LCP improved without checking INP/CLS.** A "fix" that trades one vital for another is not a fix.94- **Talking about the trace without reading the insight output.** If the agent is speculating based on code rather than trace evidence, something went wrong in step 2 or 3 — re-capture.9596## When to Escalate to the User9798- Non-determinism persists after N=3 medianing — likely real-user variance (CrUX field injection, thermal throttling, GC). Surface to the user and propose measuring in Sentry/RUM if available.99- Fix requires architectural change (e.g., switching rendering mode, moving to a CDN, adopting streaming SSR). State the hypothesis, estimated impact, and stop. Do not implement silently.100- Dependency upgrade required — confirm with the user before touching `package.json`.101102## Reference Index103104- `references/lcp.md` — LCP regression workflow: subpart model, insight sequence, prompt template.105- `references/inp.md` — INP / slow interaction workflow: Long Animation Frames, presentation split.106- `references/rerenders.md` — Unnecessary re-renders workflow: `react-scan`, classification, React DevTools integration.107- `references/bundle.md` — Bundle bloat workflow: `DuplicatedJavaScript`/`LegacyJavaScript`, network filtering, source mapping.108- `references/memory.md` — Memory leak workflow: heap snapshot comparison, `memlab`, common leak shapes.109- `references/react.md` — React-specific rules: memo discipline, `useDeferredValue`, `<Profiler>`, RSC streaming gotchas, tool recommendations.110- `references/nextjs.md` — Next.js-specific rules: Turbopack tracing, `instrumentation.ts`, RSC payload debugging, `next build` output parsing.