Performance Optimization
Overview
Systematically identify and resolve performance bottlenecks using measurement-driven methodology. This skill enforces a strict MEASURE-IDENTIFY-OPTIMIZE-VERIFY cycle, preventing premature optimization and speculation. Every optimization must produce measurable improvement or be reverted.
Announce at start: "I'm using the performance-optimization skill to diagnose and resolve bottlenecks."
Phase 1: MEASURE (Establish Baseline)
Goal: Capture real metrics before changing anything.
Actions
# Web: Lighthouse CI
npx lighthouse https://your-app.com --output=json --output-path=baseline.json
# API: load test with k6
k6 run --out json=baseline.json loadtest.js
# Database: slow query log
# PostgreSQL: SET log_min_duration_statement = 100; -- log queries > 100ms
Record these numbers. They are the baseline against which improvement is measured.
STOP — Do NOT proceed to Phase 2 until:
Phase 2: IDENTIFY (Find the Actual Bottleneck)
Goal: Use profiling tools to find WHERE time is spent. Do NOT guess.
Profiling Tool Selection Table
| Layer |
Tool |
What It Shows |
| Frontend rendering |
React DevTools Profiler, Chrome Performance tab |
Component render times |
| Network |
Chrome Network tab, WebPageTest |
Request waterfall, TTFB |
| JavaScript |
Chrome Performance tab, console.time() |
Function execution time |
| Node.js server |
--prof flag, clinic.js, 0x |
CPU flame graphs |
| Database |
EXPLAIN ANALYZE, pg_stat_statements |
Query plans, slow queries |
| Memory |
Chrome Memory tab, heapdump |
Allocation patterns, leaks |
| Bundle size |
webpack-bundle-analyzer, vite-bundle-visualizer |
Module sizes |
The bottleneck is almost never where you assume it is. Measure first.
STOP — Do NOT proceed to Phase 3 until:
Phase 3: OPTIMIZE (Fix the Identified Bottleneck)
Goal: Apply the targeted fix. Change ONE thing at a time.
Optimization Decision Table
| Bottleneck Type |
Optimization Approach |
Example |
| Large bundle |
Code splitting, tree shaking, dynamic imports |
React.lazy(() => import('./HeavyComponent')) |
| Slow API response |
Caching, query optimization, pagination |
Add Redis cache with 5min TTL |
| Slow database query |
Add index, optimize query plan, materialized view |
CREATE INDEX idx_user_email ON users(email) |
| Excessive re-renders |
Memoization, virtualization, state restructuring |
React.memo, useMemo |
| Large images |
Compression, lazy loading, responsive images |
<img loading="lazy" srcset="..."> |
| Slow TTFB |
Server-side caching, CDN, edge rendering |
Stale-while-revalidate pattern |
| Memory leak |
Fix event listener cleanup, weak references |
Proper useEffect cleanup |
STOP — Do NOT proceed to Phase 4 until:
Phase 4: VERIFY (Measure Again)
Goal: Re-run the exact same measurement from Phase 1.
Actions
- Run the same profiling/measurement as Phase 1
- Compare results:
- Did the metric improve?
- By how much?
- Did any other metrics regress?
- If improvement is not measurable, REVERT the change.
Optimization that cannot be measured is not optimization.
STOP — Verification complete when:
Caching Strategy Decision Table
| Cache Type |
Use When |
TTL Guidance |
Invalidation |
| In-memory (LRU) |
Single-instance, hot data, computed values |
Seconds to minutes |
Eviction policy |
| Redis/Memcached |
Multi-instance, shared cache, sessions |
Minutes to hours |
Event-based or TTL |
| CDN |
Static assets, public pages, API responses |
Hours to days |
Deploy-triggered purge |
| Browser |
Repeat visits, static resources |
Days to months (versioned) |
Cache-busting hash |
Cache-Control Headers
# Immutable assets (hashed filenames)
Cache-Control: public, max-age=31536000, immutable
# API responses (cacheable but must revalidate)
Cache-Control: public, max-age=0, must-revalidate
ETag: "abc123"
# Private user data
Cache-Control: private, no-store
# Stale-while-revalidate (fast response + background refresh)
Cache-Control: public, max-age=60, stale-while-revalidate=300
Bundle Optimization Techniques
| Technique |
Impact |
Implementation |
| Route-level code splitting |
High |
React.lazy() + Suspense per route |
| Tree shaking |
High |
ES modules only, sideEffects: false |
| Dynamic imports |
Medium |
await import('heavy-lib') on user action |
| Image optimization |
High |
next/image, WebP/AVIF, responsive srcset |
| Font optimization |
Medium |
next/font, font-display: swap, subset |
| Dependency replacement |
Medium |
day.js for moment.js, lodash-es for lodash |
Bundle Analysis Commands
# Webpack
npx webpack-bundle-analyzer stats.json
# Vite
npx vite-bundle-visualizer
# Next.js
ANALYZE=true next build
Database Query Tuning
Index Optimization
-- Find missing indexes (PostgreSQL)
SELECT schemaname, tablename, seq_scan, idx_scan
FROM pg_stat_user_tables
WHERE seq_scan > idx_scan
ORDER BY seq_scan DESC;
Index Rules
| Rule |
Explanation |
| Index WHERE, JOIN, ORDER BY columns |
These are the columns the DB searches |
| Equality columns first in composite index |
Most selective filtering first |
| Range columns last in composite index |
Less selective, applied after equality |
| Remove unused indexes |
They slow down writes |
| Use partial indexes for filtered queries |
Smaller index, faster lookups |
Query Plan Red Flags
| Red Flag in EXPLAIN ANALYZE |
Meaning |
Fix |
| Seq Scan on large table |
Full table scan |
Add index |
| Nested Loop with many rows |
O(n*m) join |
Add index or restructure query |
| Sort with high memory |
Sorting in memory |
Add index matching ORDER BY |
| Actual rows >> estimated rows |
Stale statistics |
Run ANALYZE |
| Hash Join with large build |
Memory-intensive |
Ensure join columns are indexed |
Web Vitals Targets
| Metric |
Good |
Needs Work |
Poor |
| LCP (Largest Contentful Paint) |
< 2.5s |
2.5-4s |
> 4s |
| INP (Interaction to Next Paint) |
< 200ms |
200-500ms |
> 500ms |
| CLS (Cumulative Layout Shift) |
< 0.1 |
0.1-0.25 |
> 0.25 |
Web Vitals Optimization Table
| Metric |
Optimization |
Implementation |
| LCP |
Preload LCP resource |
<link rel="preload"> or fetchpriority="high" |
| LCP |
Inline critical CSS |
Extract above-fold CSS inline |
| LCP |
Optimize TTFB |
CDN, edge rendering, server caching |
| INP |
Break long tasks |
requestIdleCallback, scheduler.yield() |
| INP |
Debounce input handlers |
100-300ms debounce on expensive handlers |
| INP |
Web Workers |
Move computation off main thread |
| CLS |
Explicit dimensions |
Set width/height on images and videos |
| CLS |
Reserve space for dynamic content |
Placeholder sizing for ads, embeds |
| CLS |
Use transform animations |
Avoid layout-triggering properties |
Load Testing
Test Types
| Type |
Users |
Duration |
Purpose |
| Smoke |
1-2 |
1 minute |
Verify test works |
| Load |
Expected traffic |
10-30 min |
Normal performance |
| Stress |
2-3x expected |
10-30 min |
Find breaking point |
| Soak |
Normal load |
2-8 hours |
Find memory leaks |
Key Metrics
- Response time percentiles (p50, p95, p99) — not averages
- Error rate under load
- Throughput (requests per second)
- Resource utilization (CPU, memory, connections)
Anti-Patterns / Common Mistakes
| Anti-Pattern |
Why It Is Wrong |
Correct Approach |
| Optimizing without measuring |
You do not know what to fix |
MEASURE first, always |
| Premature optimization |
Wastes time on non-bottlenecks |
Profile to find actual bottleneck |
| Memoizing everything |
Adds complexity without proven benefit |
Profile first, memoize second |
| Caching without invalidation strategy |
Stale data causes bugs |
Define invalidation before adding cache |
| Optimizing averages instead of percentiles |
Averages hide tail latency |
Track p95 and p99 |
| Multiple optimizations at once |
Cannot attribute improvement |
One change at a time |
| Keeping optimizations that do not measurably help |
Dead code and complexity |
Revert if no measurable improvement |
| Adding indexes without checking query patterns |
Unused indexes slow writes |
Check slow query log first |
Subagent Dispatch Opportunities
| Task Pattern |
Dispatch To |
When |
| Profiling different system layers concurrently |
Agent tool with subagent_type="Explore" (one per layer) |
When analyzing frontend, backend, and database independently |
| Bundle analysis and tree-shaking review |
Agent tool with subagent_type="general-purpose" |
When frontend bundle size is a concern |
| Database query optimization analysis |
Agent tool dispatching database-architect agent |
When slow queries are identified across multiple tables |
Follow the dispatching-parallel-agents skill protocol when dispatching.
Integration Points
| Skill |
Relationship |
senior-frontend |
Frontend performance uses bundle and Web Vitals optimization |
senior-backend |
Backend performance uses caching and query tuning |
testing-strategy |
Load tests are part of the testing pyramid |
code-review |
Review checks for performance regressions |
systematic-debugging |
Performance issues follow the same investigation methodology |
acceptance-testing |
Performance targets become acceptance criteria |
Skill Type
FLEXIBLE — Adapt the depth of optimization to the project context. The MEASURE-IDENTIFY-OPTIMIZE-VERIFY cycle is mandatory for every optimization. Revert any change that does not produce measurable improvement.
1---2name: performance-optimization3description: Use when optimizing application performance, reducing load times, improving database queries, meeting performance budgets, or diagnosing bottlenecks in web applications or APIs. Triggers: slow page loads, poor Web Vitals, database timeouts, large bundle size, user-reported sluggishness, scaling preparation.4---5
6# Performance Optimization
7
8## Overview
9
10Systematically identify and resolve performance bottlenecks using measurement-driven methodology. This skill enforces a strict MEASURE-IDENTIFY-OPTIMIZE-VERIFY cycle, preventing premature optimization and speculation. Every optimization must produce measurable improvement or be reverted.
11
12**Announce at start:** "I'm using the performance-optimization skill to diagnose and resolve bottlenecks."
13
14---
15
16## Phase 1: MEASURE (Establish Baseline)
17
18**Goal:** Capture real metrics before changing anything.
19
20### Actions
21
22```bash
23# Web: Lighthouse CI
24npx lighthouse https://your-app.com --output=json --output-path=baseline.json
25
26# API: load test with k6
27k6 run --out json=baseline.json loadtest.js
28
29# Database: slow query log
30# PostgreSQL: SET log_min_duration_statement = 100; -- log queries > 100ms
31```
32
33Record these numbers. They are the baseline against which improvement is measured.
34
35### STOP — Do NOT proceed to Phase 2 until:
36- [ ] Baseline metrics are captured and saved
37- [ ] Specific metric targets are defined (e.g., LCP < 2.5s)
38- [ ] Measurement methodology is documented (so it can be repeated)
39
40---
41
42## Phase 2: IDENTIFY (Find the Actual Bottleneck)
43
44**Goal:** Use profiling tools to find WHERE time is spent. Do NOT guess.
45
46### Profiling Tool Selection Table
47
48| Layer | Tool | What It Shows |
49|-------|------|--------------|
50| Frontend rendering | React DevTools Profiler, Chrome Performance tab | Component render times |
51| Network | Chrome Network tab, WebPageTest | Request waterfall, TTFB |
52| JavaScript | Chrome Performance tab, `console.time()` | Function execution time |
53| Node.js server | `--prof` flag, clinic.js, 0x | CPU flame graphs |
54| Database | `EXPLAIN ANALYZE`, pg_stat_statements | Query plans, slow queries |
55| Memory | Chrome Memory tab, heapdump | Allocation patterns, leaks |
56| Bundle size | webpack-bundle-analyzer, vite-bundle-visualizer | Module sizes |
57
58The bottleneck is almost never where you assume it is. Measure first.
59
60### STOP — Do NOT proceed to Phase 3 until:
61- [ ] Profiling tool appropriate to the layer has been used
62- [ ] Specific bottleneck is identified with data
63- [ ] Bottleneck accounts for a significant portion of the problem
64
65---
66
67## Phase 3: OPTIMIZE (Fix the Identified Bottleneck)
68
69**Goal:** Apply the targeted fix. Change ONE thing at a time.
70
71### Optimization Decision Table
72
73| Bottleneck Type | Optimization Approach | Example |
74|----------------|----------------------|---------|
75| Large bundle | Code splitting, tree shaking, dynamic imports | `React.lazy(() => import('./HeavyComponent'))` |
76| Slow API response | Caching, query optimization, pagination | Add Redis cache with 5min TTL |
77| Slow database query | Add index, optimize query plan, materialized view | `CREATE INDEX idx_user_email ON users(email)` |
78| Excessive re-renders | Memoization, virtualization, state restructuring | `React.memo`, `useMemo` |
79| Large images | Compression, lazy loading, responsive images | `<img loading="lazy" srcset="...">` |
80| Slow TTFB | Server-side caching, CDN, edge rendering | Stale-while-revalidate pattern |
81| Memory leak | Fix event listener cleanup, weak references | Proper `useEffect` cleanup |
82
83### STOP — Do NOT proceed to Phase 4 until:
84- [ ] Only ONE change has been made
85- [ ] Change directly targets the identified bottleneck
86- [ ] No unrelated changes were made alongside the optimization
87
88---
89
90## Phase 4: VERIFY (Measure Again)
91
92**Goal:** Re-run the exact same measurement from Phase 1.
93
94### Actions
95
961. Run the same profiling/measurement as Phase 1
972. Compare results:
98 - Did the metric improve?
99 - By how much?
100 - Did any other metrics regress?
1013. **If improvement is not measurable, REVERT the change.**
102
103Optimization that cannot be measured is not optimization.
104
105### STOP — Verification complete when:
106- [ ] Same measurement methodology used as Phase 1
107- [ ] Improvement is quantified (e.g., "LCP reduced from 3.2s to 2.1s")
108- [ ] No regressions in other metrics
109- [ ] If no improvement: change reverted
110
111---
112
113## Caching Strategy Decision Table
114
115| Cache Type | Use When | TTL Guidance | Invalidation |
116|------------|----------|--------------|-------------|
117| **In-memory (LRU)** | Single-instance, hot data, computed values | Seconds to minutes | Eviction policy |
118| **Redis/Memcached** | Multi-instance, shared cache, sessions | Minutes to hours | Event-based or TTL |
119| **CDN** | Static assets, public pages, API responses | Hours to days | Deploy-triggered purge |
120| **Browser** | Repeat visits, static resources | Days to months (versioned) | Cache-busting hash |
121
122### Cache-Control Headers
123
124```
125# Immutable assets (hashed filenames)
126Cache-Control: public, max-age=31536000, immutable
127
128# API responses (cacheable but must revalidate)
129Cache-Control: public, max-age=0, must-revalidate
130ETag: "abc123"
131
132# Private user data
133Cache-Control: private, no-store
134
135# Stale-while-revalidate (fast response + background refresh)
136Cache-Control: public, max-age=60, stale-while-revalidate=300
137```
138
139---
140
141## Bundle Optimization Techniques
142
143| Technique | Impact | Implementation |
144|-----------|--------|---------------|
145| Route-level code splitting | High | `React.lazy()` + `Suspense` per route |
146| Tree shaking | High | ES modules only, `sideEffects: false` |
147| Dynamic imports | Medium | `await import('heavy-lib')` on user action |
148| Image optimization | High | next/image, WebP/AVIF, responsive srcset |
149| Font optimization | Medium | `next/font`, `font-display: swap`, subset |
150| Dependency replacement | Medium | day.js for moment.js, lodash-es for lodash |
151
152### Bundle Analysis Commands
153
154```bash
155# Webpack
156npx webpack-bundle-analyzer stats.json
157
158# Vite
159npx vite-bundle-visualizer
160
161# Next.js
162ANALYZE=true next build
163```
164
165---
166
167## Database Query Tuning
168
169### Index Optimization
170
171```sql
172-- Find missing indexes (PostgreSQL)
173SELECT schemaname, tablename, seq_scan, idx_scan
174FROM pg_stat_user_tables
175WHERE seq_scan > idx_scan
176ORDER BY seq_scan DESC;
177```
178
179### Index Rules
180
181| Rule | Explanation |
182|------|------------|
183| Index WHERE, JOIN, ORDER BY columns | These are the columns the DB searches |
184| Equality columns first in composite index | Most selective filtering first |
185| Range columns last in composite index | Less selective, applied after equality |
186| Remove unused indexes | They slow down writes |
187| Use partial indexes for filtered queries | Smaller index, faster lookups |
188
189### Query Plan Red Flags
190
191| Red Flag in EXPLAIN ANALYZE | Meaning | Fix |
192|----------------------------|---------|-----|
193| **Seq Scan** on large table | Full table scan | Add index |
194| **Nested Loop** with many rows | O(n*m) join | Add index or restructure query |
195| **Sort** with high memory | Sorting in memory | Add index matching ORDER BY |
196| Actual rows >> estimated rows | Stale statistics | Run ANALYZE |
197| **Hash Join** with large build | Memory-intensive | Ensure join columns are indexed |
198
199---
200
201## Web Vitals Targets
202
203| Metric | Good | Needs Work | Poor |
204|--------|------|------------|------|
205| **LCP** (Largest Contentful Paint) | < 2.5s | 2.5-4s | > 4s |
206| **INP** (Interaction to Next Paint) | < 200ms | 200-500ms | > 500ms |
207| **CLS** (Cumulative Layout Shift) | < 0.1 | 0.1-0.25 | > 0.25 |
208
209### Web Vitals Optimization Table
210
211| Metric | Optimization | Implementation |
212|--------|-------------|---------------|
213| LCP | Preload LCP resource | `<link rel="preload">` or `fetchpriority="high"` |
214| LCP | Inline critical CSS | Extract above-fold CSS inline |
215| LCP | Optimize TTFB | CDN, edge rendering, server caching |
216| INP | Break long tasks | `requestIdleCallback`, `scheduler.yield()` |
217| INP | Debounce input handlers | 100-300ms debounce on expensive handlers |
218| INP | Web Workers | Move computation off main thread |
219| CLS | Explicit dimensions | Set `width`/`height` on images and videos |
220| CLS | Reserve space for dynamic content | Placeholder sizing for ads, embeds |
221| CLS | Use transform animations | Avoid layout-triggering properties |
222
223---
224
225## Load Testing
226
227### Test Types
228
229| Type | Users | Duration | Purpose |
230|------|-------|----------|---------|
231| Smoke | 1-2 | 1 minute | Verify test works |
232| Load | Expected traffic | 10-30 min | Normal performance |
233| Stress | 2-3x expected | 10-30 min | Find breaking point |
234| Soak | Normal load | 2-8 hours | Find memory leaks |
235
236### Key Metrics
237
238- Response time percentiles (p50, p95, p99) — not averages
239- Error rate under load
240- Throughput (requests per second)
241- Resource utilization (CPU, memory, connections)
242
243---
244
245## Anti-Patterns / Common Mistakes
246
247| Anti-Pattern | Why It Is Wrong | Correct Approach |
248|-------------|----------------|-----------------|
249| Optimizing without measuring | You do not know what to fix | MEASURE first, always |
250| Premature optimization | Wastes time on non-bottlenecks | Profile to find actual bottleneck |
251| Memoizing everything | Adds complexity without proven benefit | Profile first, memoize second |
252| Caching without invalidation strategy | Stale data causes bugs | Define invalidation before adding cache |
253| Optimizing averages instead of percentiles | Averages hide tail latency | Track p95 and p99 |
254| Multiple optimizations at once | Cannot attribute improvement | One change at a time |
255| Keeping optimizations that do not measurably help | Dead code and complexity | Revert if no measurable improvement |
256| Adding indexes without checking query patterns | Unused indexes slow writes | Check slow query log first |
257
258---
259
260## Subagent Dispatch Opportunities
261
262| Task Pattern | Dispatch To | When |
263|---|---|---|
264| Profiling different system layers concurrently | `Agent` tool with `subagent_type="Explore"` (one per layer) | When analyzing frontend, backend, and database independently |
265| Bundle analysis and tree-shaking review | `Agent` tool with `subagent_type="general-purpose"` | When frontend bundle size is a concern |
266| Database query optimization analysis | `Agent` tool dispatching `database-architect` agent | When slow queries are identified across multiple tables |
267
268Follow the `dispatching-parallel-agents` skill protocol when dispatching.
269
270---
271
272## Integration Points
273
274| Skill | Relationship |
275|-------|-------------|
276| `senior-frontend` | Frontend performance uses bundle and Web Vitals optimization |
277| `senior-backend` | Backend performance uses caching and query tuning |
278| `testing-strategy` | Load tests are part of the testing pyramid |
279| `code-review` | Review checks for performance regressions |
280| `systematic-debugging` | Performance issues follow the same investigation methodology |
281| `acceptance-testing` | Performance targets become acceptance criteria |
282
283---
284
285## Skill Type
286
287**FLEXIBLE** — Adapt the depth of optimization to the project context. The MEASURE-IDENTIFY-OPTIMIZE-VERIFY cycle is mandatory for every optimization. Revert any change that does not produce measurable improvement.