Performance Optimization
Measure before optimizing. Profile first, fix the proven bottleneck, measure again, then guard against regression.
Hard Rules
- Never optimize without a baseline measurement (synthetic + real-user when possible).
- Fix the actual bottleneck — not the most interesting code.
- One change at a time when validating; otherwise you cannot attribute wins.
- Set budgets (LCP, bundle KB, p95 latency) and fail CI when exceeded.
- Document what you measured, what you changed, and the delta.
Core Web Vitals Targets
| Metric |
Good |
Needs work |
Poor |
| LCP |
≤ 2.5s |
≤ 4.0s |
> 4.0s |
| INP |
≤ 200ms |
≤ 500ms |
> 500ms |
| CLS |
≤ 0.1 |
≤ 0.25 |
> 0.25 |
Workflow
Step 1 — Measure baseline
- Frontend: Lighthouse (synthetic), DevTools Performance,
web-vitals RUM.
- Backend: APM, query logs with timing, p50/p95/p99 on critical endpoints.
- Record environment, device, and data volume.
Step 2 — Identify bottleneck
Use symptom → probe map:
Slow first load → bundle size, render-blocking, LCP element
Slow interaction → INP, main-thread long tasks, layout thrash
Slow API → N+1 queries, missing indexes, cold starts
Memory growth → leaks, unbounded caches, retained DOM
Step 3 — Fix narrowly
Prefer highest-impact, lowest-risk fixes: indexes, caching, code-split, image sizing, defer non-critical JS.
Step 4 — Verify
Re-run the same measurement as Step 1. Report before/after numbers.
Step 5 — Guard
Add Lighthouse CI budget, bundle-size check, or perf test on the hot path.
When NOT to use
- No evidence of a problem and no stated budget
- Micro-optimizing cold paths while hot paths are unmeasured
- Replacing architecture before profiling
Gotchas
- Synthetic scores ≠ real-user experience — use both.
- Optimizing the wrong layer (CSS when the DB is the bottleneck).
- Caching without invalidation creates subtle bugs.
- Bundle "tree-shaking" claims without measuring shipped bytes.
Common Rationalizations
| Excuse |
Reality |
| "It feels fast on my machine" |
Your machine is not production traffic or network. |
| "We'll add monitoring later" |
Without baselines you cannot prove improvement. |
| "Let's rewrite in Rust" |
Profile first — often the query or algorithm is the issue. |
| "Lighthouse is enough" |
Lab scores miss real devices and cache states. |
| "Ship now, optimize later" |
Regressions are cheaper to block in CI than fix in prod. |
Output Format
## Performance report — [area]
Baseline: [metrics + how measured]
Bottleneck: [evidence]
Change: [what]
After: [metrics]
Guard: [CI/monitoring added]
Examples
Verification
Red Flags
- Optimization started without baseline measurement
- CSS or frontend tuned while DB is actual bottleneck
- Multiple changes shipped — win cannot be attributed
- Synthetic score improved but real-user metrics flat
Prune Log
Last pruned: 2026-07-04
- No changes — citation audit passed; content current (improve-skills full pass 2026-07-04)
Impact Report
Area: [frontend/backend] | Baseline: [key metric]
Bottleneck: [one line] | Delta: [before → after]
Guard: [added/deferred]
1---2name: performance-optimization3description: Measure, profile, and fix performance bottlenecks — Core Web Vitals, backend latency, bundle size, and database queries. Load when performance requirements exist, users report slowness, profiling reveals bottlenecks, or the user asks to optimize load time, LCP, INP, CLS, or response time. Not for premature optimization without evidence. Pairs with browser-testing-with-devtools and ci-cd-and-automation for regression guards.4license: MIT5---67# Performance Optimization89Measure before optimizing. Profile first, fix the proven bottleneck, measure again, then guard against regression.1011## Hard Rules1213- Never optimize without a **baseline measurement** (synthetic + real-user when possible).14- Fix the **actual bottleneck** — not the most interesting code.15- One change at a time when validating; otherwise you cannot attribute wins.16- Set **budgets** (LCP, bundle KB, p95 latency) and fail CI when exceeded.17- Document what you measured, what you changed, and the delta.1819---2021## Core Web Vitals Targets2223| Metric | Good | Needs work | Poor |24|--------|------|------------|------|25| LCP | ≤ 2.5s | ≤ 4.0s | > 4.0s |26| INP | ≤ 200ms | ≤ 500ms | > 500ms |27| CLS | ≤ 0.1 | ≤ 0.25 | > 0.25 |2829---3031## Workflow3233### Step 1 — Measure baseline3435- **Frontend:** Lighthouse (synthetic), DevTools Performance, `web-vitals` RUM.36- **Backend:** APM, query logs with timing, p50/p95/p99 on critical endpoints.37- Record environment, device, and data volume.3839### Step 2 — Identify bottleneck4041Use symptom → probe map:4243```44Slow first load → bundle size, render-blocking, LCP element45Slow interaction → INP, main-thread long tasks, layout thrash46Slow API → N+1 queries, missing indexes, cold starts47Memory growth → leaks, unbounded caches, retained DOM48```4950### Step 3 — Fix narrowly5152Prefer highest-impact, lowest-risk fixes: indexes, caching, code-split, image sizing, defer non-critical JS.5354### Step 4 — Verify5556Re-run the **same measurement** as Step 1. Report before/after numbers.5758### Step 5 — Guard5960Add Lighthouse CI budget, bundle-size check, or perf test on the hot path.6162---6364## When NOT to use6566- No evidence of a problem and no stated budget67- Micro-optimizing cold paths while hot paths are unmeasured68- Replacing architecture before profiling6970---7172## Gotchas7374- Synthetic scores ≠ real-user experience — use both.75- Optimizing the wrong layer (CSS when the DB is the bottleneck).76- Caching without invalidation creates subtle bugs.77- Bundle "tree-shaking" claims without measuring shipped bytes.7879---8081## Common Rationalizations8283| Excuse | Reality |84|--------|---------|85| "It feels fast on my machine" | Your machine is not production traffic or network. |86| "We'll add monitoring later" | Without baselines you cannot prove improvement. |87| "Let's rewrite in Rust" | Profile first — often the query or algorithm is the issue. |88| "Lighthouse is enough" | Lab scores miss real devices and cache states. |89| "Ship now, optimize later" | Regressions are cheaper to block in CI than fix in prod. |9091---9293## Output Format9495```markdown96## Performance report — [area]9798Baseline: [metrics + how measured]99Bottleneck: [evidence]100Change: [what]101After: [metrics]102Guard: [CI/monitoring added]103```104105---106107## Examples108109<examples>110 <example>111 <input>"Homepage LCP is 5s in CrUX."</input>112 <output>Measure LCP element (hero image). Compress, preload, fix CLS from font swap. Re-run Lighthouse + verify CrUX trend.</output>113 </example>114</examples>115116---117118## Verification119120- [ ] Baseline captured with method and environment noted121- [ ] Bottleneck identified with evidence (not assumption)122- [ ] After metrics show improvement on the same probe123- [ ] Regression guard added or explicitly deferred with reason124- [ ] No premature micro-opts on unmeasured paths125126---127128## Red Flags129130- Optimization started without baseline measurement131- CSS or frontend tuned while DB is actual bottleneck132- Multiple changes shipped — win cannot be attributed133- Synthetic score improved but real-user metrics flat134135## Prune Log136Last pruned: 2026-07-04137- No changes — citation audit passed; content current (improve-skills full pass 2026-07-04)138139140## Impact Report141142```143Area: [frontend/backend] | Baseline: [key metric]144Bottleneck: [one line] | Delta: [before → after]145Guard: [added/deferred]146```