Performance Optimization Rules
1. Core Web Vitals
The three metrics Google uses for page experience ranking.
| Metric |
Good |
Needs Improvement |
Poor |
| LCP (Largest Contentful Paint) |
≤ 2.5s |
2.5s – 4.0s |
> 4.0s |
| INP (Interaction to Next Paint) |
≤ 200ms |
200ms – 500ms |
> 500ms |
| CLS (Cumulative Layout Shift) |
≤ 0.1 |
0.1 – 0.25 |
> 0.25 |
- INP replaced FID as a Core Web Vital in March 2024
- INP measures total interaction latency (input delay + processing + presentation)
- Measure with field data (CrUX,
web-vitals library) and lab data (Lighthouse)
2. Frontend — Bundle Optimization
Code Splitting
- Split by route (most effective for initial load)
- Use dynamic
import() for non-critical modules
- Separate vendor chunks from application code
// React route-based splitting
const Dashboard = React.lazy(() => import("./Dashboard"));
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
Tree Shaking
- Use ES modules (
import/export) — CommonJS is not tree-shakeable
- Set
"sideEffects": false in package.json
- Avoid barrel files (
index.ts re-exports) for large libraries
Bundle Analysis
- Use
webpack-bundle-analyzer, source-map-explorer, or vite-bundle-visualizer
- Identify and eliminate duplicate dependencies
- Set performance budgets (see Section 8)
3. Frontend — Image Optimization
| Technique |
Impact |
| Modern formats (WebP, AVIF) |
25-50% smaller than JPEG/PNG |
Responsive images (srcset + sizes) |
Serve viewport-appropriate size |
Lazy loading (loading="lazy") |
Defer offscreen images |
Explicit dimensions (width/height) |
Prevent CLS |
fetchpriority="high" on LCP image |
Prioritize critical image (limit to 1-2 images to avoid priority contention) |
| CDN image transformation |
On-demand resize and format conversion |
<img
src="hero.webp"
srcset="hero-480.webp 480w, hero-800.webp 800w, hero-1200.webp 1200w"
sizes="(max-width: 600px) 480px, (max-width: 1000px) 800px, 1200px"
width="1200" height="630"
loading="lazy"
alt="Hero image"
/>
4. Frontend — Rendering Performance
Minimize Reflow/Repaint
- Batch DOM reads before writes (avoid interleaving)
- Use
transform and opacity for animations (GPU-composited, no reflow)
- Use
requestAnimationFrame for DOM mutations
- Use
content-visibility: auto for offscreen content
List Virtualization
Render only visible items for large lists (1000+ items).
- React:
@tanstack/react-virtual, react-window
- Vue:
vue-virtual-scroller
Avoid Main Thread Blocking
- Break long tasks (> 50ms) with
scheduler.yield() or setTimeout
- Offload heavy computation to Web Workers
- Use
requestIdleCallback for non-urgent work
React-Specific
- Use
React.memo for expensive components
- Use
useMemo/useCallback for referential stability (not premature optimization)
- Avoid creating objects/arrays in render
5. Backend — Database Query Tuning
Index Strategy
| Type |
Use Case |
| B-Tree (default) |
Range queries, sorting, equality |
| Composite index |
Multi-column WHERE, column order matters |
| Covering index |
Query answered from index only |
| Partial index |
Index subset of rows (PostgreSQL) |
- Always use
EXPLAIN ANALYZE to verify query plans
- Remove unused indexes (they slow down writes)
N+1 Problem
# BAD: N+1 queries
users = User.query.all() # 1 query
for user in users:
print(user.orders) # N queries
# GOOD: Eager loading
users = User.query.options(joinedload(User.orders)).all() # 1 query
General Rules
- Select only needed columns (avoid
SELECT *)
- Use cursor-based pagination over offset-based for large datasets
- Use prepared statements (security + plan caching)
- Monitor slow query logs
6. Backend — Connection and Response
Connection Pooling
- Reuse database connections instead of creating per request
- Pool size guideline (HikariCP/PostgreSQL):
connections = (CPU cores × 2) + effective_spindle_count — adjust for other databases
- Set idle timeout and max lifetime
- Tools: HikariCP (Java),
pg-pool (Node.js), PgBouncer (PostgreSQL)
Response Compression
| Algorithm |
Compression |
Speed |
Support |
| gzip |
Good |
Medium |
Universal |
| Brotli (br) |
Better (15-25% over gzip) |
Slow compress, fast decompress |
Modern browsers (HTTPS) |
| zstd |
Better |
Fast |
Chrome 123+ |
- Apply to text resources (HTML, CSS, JS, JSON, SVG)
- Skip already-compressed formats (JPEG, PNG, WOFF2)
- Pre-compress static assets at build time
- Set
Vary: Accept-Encoding header
7. Network — Caching Strategy
Cache-Control Patterns
| Resource Type |
Recommended Header |
| Hashed static assets (JS, CSS) |
Cache-Control: public, max-age=31536000, immutable |
| HTML documents |
Cache-Control: no-cache |
| API responses (cacheable) |
Cache-Control: public, max-age=60, stale-while-revalidate=300 |
| Sensitive data |
Cache-Control: private, no-store |
ETag / Conditional Requests
- Server sends
ETag (content hash) with response
- Client sends
If-None-Match on subsequent requests
- Server returns
304 Not Modified if unchanged (saves bandwidth)
CDN
- Serve static assets from edge servers
- Use content-hash filenames for cache busting (
app.a1b2c3.js)
- Set long
max-age + immutable for hashed assets
- Use
s-maxage for CDN-specific TTL
Service Worker Caching
| Strategy |
Use Case |
| Cache First |
Static assets, fonts |
| Network First |
API responses, dynamic content |
| Stale While Revalidate |
Frequently updated but stale-tolerant data |
For detailed caching patterns, see the caching skill.
8. Performance Budget and CI
Define Budgets
| Metric |
Budget Example |
| JS bundle (compressed) |
≤ 200 KB |
| Total page weight |
≤ 500 KB |
| LCP |
≤ 2.5s |
| INP |
≤ 200ms |
| CLS |
≤ 0.1 |
CI Integration
// .lighthouserc.js
module.exports = {
ci: {
assert: {
assertions: {
"largest-contentful-paint": ["error", { maxNumericValue: 2500 }],
"interactive": ["error", { maxNumericValue: 3800 }],
"cumulative-layout-shift": ["error", { maxNumericValue: 0.1 }],
},
},
},
};
Tools for CI:
- Lighthouse CI (
lhci): Core Web Vitals assertions
- size-limit: JS cost budget (size + execution time)
- bundlesize: Per-file size limits
- Webpack
performance: Asset and entrypoint size hints
9. Measurement Tools
| Tool |
Type |
Best For |
web-vitals library |
Field (RUM) |
Real user Core Web Vitals |
| CrUX (Chrome UX Report) |
Field |
Population-level metrics |
| Lighthouse |
Lab |
Comprehensive audit |
| Chrome DevTools Performance |
Lab |
Detailed profiling |
| WebPageTest |
Lab |
Multi-location, filmstrip |
| Server-Timing header |
Server |
Backend timing breakdown |
Server-Timing
Server-Timing: db;dur=53, app;dur=47.2, cache;desc="Cache Read";dur=23.2
Exposes server-side metrics in DevTools Network tab. Avoid exposing
sensitive internals in production.
10. Common Anti-Patterns
For detailed anti-patterns organized by layer, see
references/anti-patterns.md.
| Anti-Pattern |
Impact |
Fix |
| Single large bundle |
Slow initial load |
Code splitting + lazy loading |
| No image optimization |
Bandwidth waste, slow LCP |
WebP/AVIF, srcset, lazy loading |
| Missing cache headers |
Unnecessary server requests |
Proper Cache-Control |
| N+1 queries |
DB overload |
Eager loading, batch queries |
| No connection pooling |
Connection exhaustion |
Pool with proper sizing |
| No compression |
Bandwidth waste |
gzip/Brotli |
| Layout shifts |
Poor CLS |
Explicit dimensions, font-display |
| Render-blocking resources |
Slow FCP/LCP |
defer/async, critical CSS |
| Unbounded in-memory cache |
OOM risk |
TTL, LRU eviction, external cache |
| No performance budget |
Gradual regression |
CI enforcement |
1---2name: performance-optimization3description: Performance optimization patterns for frontend and backend applications. Covers Core Web Vitals (LCP, INP, CLS), bundle optimization, image optimization, rendering performance, DB query tuning, connection pooling, HTTP caching, CDN strategies, compression, performance budgets, and CI integration. Use when optimizing application performance, diagnosing slow pages or APIs, or setting up performance monitoring and budgets.4license: MIT5---67# Performance Optimization Rules89## 1. Core Web Vitals1011The three metrics Google uses for page experience ranking.1213| Metric | Good | Needs Improvement | Poor |14| --- | --- | --- | --- |15| **LCP** (Largest Contentful Paint) | ≤ 2.5s | 2.5s – 4.0s | > 4.0s |16| **INP** (Interaction to Next Paint) | ≤ 200ms | 200ms – 500ms | > 500ms |17| **CLS** (Cumulative Layout Shift) | ≤ 0.1 | 0.1 – 0.25 | > 0.25 |1819- INP replaced FID as a Core Web Vital in March 202420- INP measures total interaction latency (input delay + processing + presentation)21- Measure with field data (CrUX, `web-vitals` library) and lab data (Lighthouse)2223## 2. Frontend — Bundle Optimization2425### Code Splitting2627- Split by route (most effective for initial load)28- Use dynamic `import()` for non-critical modules29- Separate vendor chunks from application code3031```jsx32// React route-based splitting33const Dashboard = React.lazy(() => import("./Dashboard"));3435<Suspense fallback={<Loading />}>36 <Dashboard />37</Suspense>38```3940### Tree Shaking4142- Use ES modules (`import`/`export`) — CommonJS is not tree-shakeable43- Set `"sideEffects": false` in `package.json`44- Avoid barrel files (`index.ts` re-exports) for large libraries4546### Bundle Analysis4748- Use `webpack-bundle-analyzer`, `source-map-explorer`, or `vite-bundle-visualizer`49- Identify and eliminate duplicate dependencies50- Set performance budgets (see Section 8)5152## 3. Frontend — Image Optimization5354| Technique | Impact |55| --- | --- |56| Modern formats (WebP, AVIF) | 25-50% smaller than JPEG/PNG |57| Responsive images (`srcset` + `sizes`) | Serve viewport-appropriate size |58| Lazy loading (`loading="lazy"`) | Defer offscreen images |59| Explicit dimensions (`width`/`height`) | Prevent CLS |60| `fetchpriority="high"` on LCP image | Prioritize critical image (limit to 1-2 images to avoid priority contention) |61| CDN image transformation | On-demand resize and format conversion |6263```html64<img65 src="hero.webp"66 srcset="hero-480.webp 480w, hero-800.webp 800w, hero-1200.webp 1200w"67 sizes="(max-width: 600px) 480px, (max-width: 1000px) 800px, 1200px"68 width="1200" height="630"69 loading="lazy"70 alt="Hero image"71/>72```7374## 4. Frontend — Rendering Performance7576### Minimize Reflow/Repaint7778- Batch DOM reads before writes (avoid interleaving)79- Use `transform` and `opacity` for animations (GPU-composited, no reflow)80- Use `requestAnimationFrame` for DOM mutations81- Use `content-visibility: auto` for offscreen content8283### List Virtualization8485Render only visible items for large lists (1000+ items).8687- React: `@tanstack/react-virtual`, `react-window`88- Vue: `vue-virtual-scroller`8990### Avoid Main Thread Blocking9192- Break long tasks (> 50ms) with `scheduler.yield()` or `setTimeout`93- Offload heavy computation to Web Workers94- Use `requestIdleCallback` for non-urgent work9596### React-Specific9798- Use `React.memo` for expensive components99- Use `useMemo`/`useCallback` for referential stability (not premature optimization)100- Avoid creating objects/arrays in render101102## 5. Backend — Database Query Tuning103104### Index Strategy105106| Type | Use Case |107| --- | --- |108| B-Tree (default) | Range queries, sorting, equality |109| Composite index | Multi-column WHERE, column order matters |110| Covering index | Query answered from index only |111| Partial index | Index subset of rows (PostgreSQL) |112113- Always use `EXPLAIN ANALYZE` to verify query plans114- Remove unused indexes (they slow down writes)115116### N+1 Problem117118```python119# BAD: N+1 queries120users = User.query.all() # 1 query121for user in users:122 print(user.orders) # N queries123124# GOOD: Eager loading125users = User.query.options(joinedload(User.orders)).all() # 1 query126```127128### General Rules129130- Select only needed columns (avoid `SELECT *`)131- Use cursor-based pagination over offset-based for large datasets132- Use prepared statements (security + plan caching)133- Monitor slow query logs134135## 6. Backend — Connection and Response136137### Connection Pooling138139- Reuse database connections instead of creating per request140- Pool size guideline (HikariCP/PostgreSQL): `connections = (CPU cores × 2) + effective_spindle_count` — adjust for other databases141- Set idle timeout and max lifetime142- Tools: HikariCP (Java), `pg-pool` (Node.js), PgBouncer (PostgreSQL)143144### Response Compression145146| Algorithm | Compression | Speed | Support |147| --- | --- | --- | --- |148| gzip | Good | Medium | Universal |149| Brotli (br) | Better (15-25% over gzip) | Slow compress, fast decompress | Modern browsers (HTTPS) |150| zstd | Better | Fast | Chrome 123+ |151152- Apply to text resources (HTML, CSS, JS, JSON, SVG)153- Skip already-compressed formats (JPEG, PNG, WOFF2)154- Pre-compress static assets at build time155- Set `Vary: Accept-Encoding` header156157## 7. Network — Caching Strategy158159### Cache-Control Patterns160161| Resource Type | Recommended Header |162| --- | --- |163| Hashed static assets (JS, CSS) | `Cache-Control: public, max-age=31536000, immutable` |164| HTML documents | `Cache-Control: no-cache` |165| API responses (cacheable) | `Cache-Control: public, max-age=60, stale-while-revalidate=300` |166| Sensitive data | `Cache-Control: private, no-store` |167168### ETag / Conditional Requests169170- Server sends `ETag` (content hash) with response171- Client sends `If-None-Match` on subsequent requests172- Server returns `304 Not Modified` if unchanged (saves bandwidth)173174### CDN175176- Serve static assets from edge servers177- Use content-hash filenames for cache busting (`app.a1b2c3.js`)178- Set long `max-age` + `immutable` for hashed assets179- Use `s-maxage` for CDN-specific TTL180181### Service Worker Caching182183| Strategy | Use Case |184| --- | --- |185| Cache First | Static assets, fonts |186| Network First | API responses, dynamic content |187| Stale While Revalidate | Frequently updated but stale-tolerant data |188189For detailed caching patterns, see the [caching skill](../caching/SKILL.md).190191## 8. Performance Budget and CI192193### Define Budgets194195| Metric | Budget Example |196| --- | --- |197| JS bundle (compressed) | ≤ 200 KB |198| Total page weight | ≤ 500 KB |199| LCP | ≤ 2.5s |200| INP | ≤ 200ms |201| CLS | ≤ 0.1 |202203### CI Integration204205```javascript206// .lighthouserc.js207module.exports = {208 ci: {209 assert: {210 assertions: {211 "largest-contentful-paint": ["error", { maxNumericValue: 2500 }],212 "interactive": ["error", { maxNumericValue: 3800 }],213 "cumulative-layout-shift": ["error", { maxNumericValue: 0.1 }],214 },215 },216 },217};218```219220Tools for CI:221222- **Lighthouse CI** (`lhci`): Core Web Vitals assertions223- **size-limit**: JS cost budget (size + execution time)224- **bundlesize**: Per-file size limits225- **Webpack `performance`**: Asset and entrypoint size hints226227## 9. Measurement Tools228229| Tool | Type | Best For |230| --- | --- | --- |231| `web-vitals` library | Field (RUM) | Real user Core Web Vitals |232| CrUX (Chrome UX Report) | Field | Population-level metrics |233| Lighthouse | Lab | Comprehensive audit |234| Chrome DevTools Performance | Lab | Detailed profiling |235| WebPageTest | Lab | Multi-location, filmstrip |236| Server-Timing header | Server | Backend timing breakdown |237238### Server-Timing239240```http241Server-Timing: db;dur=53, app;dur=47.2, cache;desc="Cache Read";dur=23.2242```243244Exposes server-side metrics in DevTools Network tab. Avoid exposing245sensitive internals in production.246247## 10. Common Anti-Patterns248249For detailed anti-patterns organized by layer, see250[references/anti-patterns.md](references/anti-patterns.md).251252| Anti-Pattern | Impact | Fix |253| --- | --- | --- |254| Single large bundle | Slow initial load | Code splitting + lazy loading |255| No image optimization | Bandwidth waste, slow LCP | WebP/AVIF, srcset, lazy loading |256| Missing cache headers | Unnecessary server requests | Proper Cache-Control |257| N+1 queries | DB overload | Eager loading, batch queries |258| No connection pooling | Connection exhaustion | Pool with proper sizing |259| No compression | Bandwidth waste | gzip/Brotli |260| Layout shifts | Poor CLS | Explicit dimensions, font-display |261| Render-blocking resources | Slow FCP/LCP | defer/async, critical CSS |262| Unbounded in-memory cache | OOM risk | TTL, LRU eviction, external cache |263| No performance budget | Gradual regression | CI enforcement |