mirror of
https://github.com/addyosmani/agent-skills.git
synced 2026-08-12 18:07:26 +02:00
Improve performance-optimization skill with clearer guidance
- Distinguish synthetic (Lighthouse) vs RUM (web-vitals) measurement approaches, clarifying when each is appropriate: synthetic for CI regression detection, RUM to validate real user impact. - Expand TTFB diagnosis from a single vague hint into a decision tree that breaks down each component (DNS, TCP/TLS, server processing) with specific next steps. Mirrors the tree in the skill and adds a dedicated checklist section. - Fix image optimization example: the previous "GOOD" example applied loading="lazy" without distinguishing the LCP hero image from below-the-fold images. Hero images must never be lazy-loaded. New example separates both cases explicitly. - Add art direction + resolution switching to the hero image example using <picture> with media queries for mobile/desktop crops and srcset for density variants. Mobile-first: <img src> fallback points to the mobile version. Covers AVIF → WebP → JPG format cascade and fetchpriority="high" for LCP. - Correct the date-fns tree-shaking example: modern bundlers (Vite, webpack 5+) handle named imports automatically. The "BAD" pattern was not actually bad, and following it could lead to unnecessary micro-optimizations. Real gains come from dynamic imports and route-level code splitting, which the example now illustrates instead.
This commit is contained in:
@@ -18,6 +18,14 @@ Quick reference checklist for web application performance. Use alongside the `pe
|
||||
| INP (Interaction to Next Paint) | ≤ 200ms | ≤ 500ms | > 500ms |
|
||||
| CLS (Cumulative Layout Shift) | ≤ 0.1 | ≤ 0.25 | > 0.25 |
|
||||
|
||||
## TTFB Diagnosis
|
||||
|
||||
When TTFB is slow (> 600ms), check each component in DevTools Network waterfall:
|
||||
|
||||
- [ ] **DNS resolution** slow → add `<link rel="dns-prefetch">` or `<link rel="preconnect">` for known origins
|
||||
- [ ] **TCP/TLS handshake** slow → enable HTTP/2, consider edge deployment, verify keep-alive
|
||||
- [ ] **Server processing** slow → profile backend, check slow queries, add caching
|
||||
|
||||
## Frontend Checklist
|
||||
|
||||
### Images
|
||||
|
||||
@@ -39,13 +39,18 @@ Measure before optimizing. Performance work without measurement is guessing —
|
||||
|
||||
### Step 1: Measure
|
||||
|
||||
Two complementary approaches — use both:
|
||||
|
||||
- **Synthetic (Lighthouse, DevTools Performance tab):** Controlled conditions, reproducible. Best for CI regression detection and isolating specific issues.
|
||||
- **RUM (web-vitals library, CrUX):** Real user data in real conditions. Required to validate that a fix actually improved user experience.
|
||||
|
||||
**Frontend:**
|
||||
```bash
|
||||
# Lighthouse in Chrome DevTools (or CI)
|
||||
# Synthetic: Lighthouse in Chrome DevTools (or CI)
|
||||
# Chrome DevTools → Performance tab → Record
|
||||
# Chrome DevTools MCP → Performance trace
|
||||
|
||||
# Web Vitals library in code
|
||||
# RUM: Web Vitals library in code
|
||||
import { onLCP, onINP, onCLS } from 'web-vitals';
|
||||
|
||||
onLCP(console.log);
|
||||
@@ -73,7 +78,10 @@ Use the symptom to decide what to measure first:
|
||||
What is slow?
|
||||
├── First page load
|
||||
│ ├── Large bundle? --> Measure bundle size, check code splitting
|
||||
│ ├── Slow server response? --> Measure TTFB, check API/database
|
||||
│ ├── Slow server response? --> Measure TTFB in DevTools Network waterfall
|
||||
│ │ ├── DNS long? --> Add dns-prefetch / preconnect for known origins
|
||||
│ │ ├── TCP/TLS long? --> Enable HTTP/2, check edge deployment, keep-alive
|
||||
│ │ └── Waiting (server) long? --> Profile backend, check queries and caching
|
||||
│ └── Render-blocking resources? --> Check network waterfall for CSS/JS blocking
|
||||
├── Interaction feels sluggish
|
||||
│ ├── UI freezes on click? --> Profile main thread, look for long tasks (>50ms)
|
||||
@@ -144,18 +152,56 @@ const tasks = await db.tasks.findMany({
|
||||
#### Missing Image Optimization (Frontend)
|
||||
|
||||
```html
|
||||
<!-- BAD: No dimensions, no lazy loading, no responsive sizes -->
|
||||
<!-- BAD: No dimensions, no format optimization -->
|
||||
<img src="/hero.jpg" />
|
||||
|
||||
<!-- GOOD: Responsive, lazy-loaded, properly sized -->
|
||||
<!-- GOOD: Hero / LCP image — art direction + resolution switching, high priority -->
|
||||
<!--
|
||||
Two techniques combined:
|
||||
- Art direction (media): different crop/composition per breakpoint
|
||||
- Resolution switching (srcset + sizes): right file size per screen density
|
||||
-->
|
||||
<picture>
|
||||
<!-- Mobile: portrait crop -->
|
||||
<source
|
||||
media="(max-width: 767px)"
|
||||
srcset="/hero-mobile-400.avif 400w, /hero-mobile-800.avif 800w"
|
||||
sizes="100vw"
|
||||
type="image/avif"
|
||||
/>
|
||||
<source
|
||||
media="(max-width: 767px)"
|
||||
srcset="/hero-mobile-400.webp 400w, /hero-mobile-800.webp 800w"
|
||||
sizes="100vw"
|
||||
type="image/webp"
|
||||
/>
|
||||
<!-- Desktop: landscape crop -->
|
||||
<source
|
||||
srcset="/hero-800.avif 800w, /hero-1200.avif 1200w, /hero-1600.avif 1600w"
|
||||
sizes="100vw"
|
||||
type="image/avif"
|
||||
/>
|
||||
<source
|
||||
srcset="/hero-800.webp 800w, /hero-1200.webp 1200w, /hero-1600.webp 1600w"
|
||||
sizes="100vw"
|
||||
type="image/webp"
|
||||
/>
|
||||
<img
|
||||
src="/hero-mobile.jpg"
|
||||
width="1200"
|
||||
height="600"
|
||||
fetchpriority="high"
|
||||
alt="Hero image description"
|
||||
/>
|
||||
</picture>
|
||||
|
||||
<!-- GOOD: Below-the-fold image — lazy loaded -->
|
||||
<img
|
||||
src="/hero.jpg"
|
||||
srcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w"
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
width="1200"
|
||||
height="600"
|
||||
src="/content.webp"
|
||||
width="800"
|
||||
height="400"
|
||||
loading="lazy"
|
||||
alt="Hero image description"
|
||||
alt="Content image description"
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -188,14 +234,14 @@ function TaskStats({ tasks }: Props) {
|
||||
#### Large Bundle Size
|
||||
|
||||
```typescript
|
||||
// BAD: Importing entire library
|
||||
import { format } from 'date-fns';
|
||||
|
||||
// GOOD: Tree-shakable import (if the library supports it)
|
||||
import { format } from 'date-fns/format';
|
||||
// Modern bundlers (Vite, webpack 5+) handle named imports with tree-shaking automatically.
|
||||
// Profile before changing import styles — the real gains come from splitting and lazy loading.
|
||||
|
||||
// GOOD: Dynamic import for heavy, rarely-used features
|
||||
const ChartLibrary = lazy(() => import('./ChartLibrary'));
|
||||
|
||||
// GOOD: Route-level code splitting
|
||||
const SettingsPage = lazy(() => import('./pages/Settings'));
|
||||
```
|
||||
|
||||
#### Missing Caching (Backend)
|
||||
|
||||
Reference in New Issue
Block a user