claude-mpm 4.17.0__py3-none-any.whl → 4.17.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +567 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- {claude_mpm-4.17.0.dist-info → claude_mpm-4.17.1.dist-info}/METADATA +1 -1
- {claude_mpm-4.17.0.dist-info → claude_mpm-4.17.1.dist-info}/RECORD +27 -7
- {claude_mpm-4.17.0.dist-info → claude_mpm-4.17.1.dist-info}/WHEEL +0 -0
- {claude_mpm-4.17.0.dist-info → claude_mpm-4.17.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.17.0.dist-info → claude_mpm-4.17.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.17.0.dist-info → claude_mpm-4.17.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,2305 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill_id: web-performance-optimization
|
|
3
|
+
skill_version: 0.1.0
|
|
4
|
+
description: Comprehensive guide to optimizing web performance using Lighthouse metrics and Core Web Vitals, covering modern optimization techniques and measurement strategies.
|
|
5
|
+
updated_at: 2025-10-30T17:00:00Z
|
|
6
|
+
tags: [performance, optimization, lighthouse, core-web-vitals, frontend]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Web Performance Optimization
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
Comprehensive guide to optimizing web performance using Lighthouse metrics and Core Web Vitals (updated for 2024-2025). This skill covers modern performance optimization techniques, measurement strategies, and framework-specific optimizations to deliver fast, responsive user experiences.
|
|
14
|
+
|
|
15
|
+
Web performance directly impacts user experience, conversions, and SEO rankings. Studies show that a 1-second delay can result in 7% conversion loss, while 0.1s improvements can increase conversions by 8%. This guide provides actionable strategies to optimize every aspect of web performance.
|
|
16
|
+
|
|
17
|
+
## When to Use This Skill
|
|
18
|
+
|
|
19
|
+
- Auditing website performance metrics
|
|
20
|
+
- Improving Lighthouse scores and Core Web Vitals
|
|
21
|
+
- Optimizing page load times and interactivity
|
|
22
|
+
- Reducing resource sizes and improving caching
|
|
23
|
+
- Enhancing SEO rankings through performance signals
|
|
24
|
+
- Improving user experience and conversion rates
|
|
25
|
+
- Debugging performance bottlenecks
|
|
26
|
+
- Setting up performance monitoring and budgets
|
|
27
|
+
- Implementing modern performance patterns (streaming SSR, islands architecture)
|
|
28
|
+
|
|
29
|
+
## Core Web Vitals (2024-2025)
|
|
30
|
+
|
|
31
|
+
Core Web Vitals are Google's essential metrics for measuring user experience. These metrics are ranking factors for SEO and directly correlate with user satisfaction and conversions.
|
|
32
|
+
|
|
33
|
+
### LCP (Largest Contentful Paint)
|
|
34
|
+
|
|
35
|
+
**Target:** ≤2.5 seconds (Good), 2.5-4.0s (Needs Improvement), >4.0s (Poor)
|
|
36
|
+
|
|
37
|
+
**What it Measures:** Time until the largest content element (image, video, or text block) becomes visible in the viewport. For 73% of sites, LCP is caused by images.
|
|
38
|
+
|
|
39
|
+
**Optimization Strategies:**
|
|
40
|
+
|
|
41
|
+
#### 1. Image Optimization
|
|
42
|
+
```html
|
|
43
|
+
<!-- Use modern formats with fallbacks -->
|
|
44
|
+
<picture>
|
|
45
|
+
<source srcset="hero.avif" type="image/avif">
|
|
46
|
+
<source srcset="hero.webp" type="image/webp">
|
|
47
|
+
<img src="hero.jpg" alt="Hero image" fetchpriority="high">
|
|
48
|
+
</picture>
|
|
49
|
+
|
|
50
|
+
<!-- Responsive images with srcset -->
|
|
51
|
+
<img
|
|
52
|
+
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
|
|
53
|
+
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
|
|
54
|
+
src="hero-800.webp"
|
|
55
|
+
alt="Hero image"
|
|
56
|
+
fetchpriority="high"
|
|
57
|
+
>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### 2. Preload Critical Resources
|
|
61
|
+
```html
|
|
62
|
+
<!-- Preload LCP image -->
|
|
63
|
+
<link rel="preload" as="image" href="hero.webp" fetchpriority="high">
|
|
64
|
+
|
|
65
|
+
<!-- Preload critical fonts -->
|
|
66
|
+
<link rel="preload" as="font" type="font/woff2" href="/fonts/main.woff2" crossorigin>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### 3. Server-Side Optimizations
|
|
70
|
+
```nginx
|
|
71
|
+
# Enable 103 Early Hints (25-37% improvement)
|
|
72
|
+
# Nginx configuration
|
|
73
|
+
add_header Link "</css/critical.css>; rel=preload; as=style" always;
|
|
74
|
+
add_header Link "</fonts/main.woff2>; rel=preload; as=font; crossorigin" always;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### 4. CDN and Image Services
|
|
78
|
+
```javascript
|
|
79
|
+
// Cloudinary automatic optimization
|
|
80
|
+
const imageUrl = cloudinary.url('sample.jpg', {
|
|
81
|
+
fetch_format: 'auto', // Automatically choose WebP/AVIF
|
|
82
|
+
quality: 'auto', // Optimize quality
|
|
83
|
+
width: 800,
|
|
84
|
+
crop: 'scale'
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### 5. Lazy Loading (Non-LCP Images Only)
|
|
89
|
+
```html
|
|
90
|
+
<!-- DO NOT lazy load LCP images -->
|
|
91
|
+
<img src="hero.webp" alt="Hero" fetchpriority="high">
|
|
92
|
+
|
|
93
|
+
<!-- DO lazy load below-the-fold images -->
|
|
94
|
+
<img src="content.webp" alt="Content" loading="lazy">
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### INP (Interaction to Next Paint) - NEW March 2024
|
|
98
|
+
|
|
99
|
+
**Target:** ≤200ms (Good), 200-500ms (Needs Improvement), >500ms (Poor)
|
|
100
|
+
|
|
101
|
+
**What it Measures:** Responsiveness to user interactions (clicks, taps, keyboard input). INP replaced FID (First Input Delay) as a Core Web Vital in March 2024, providing a more comprehensive measure of interaction responsiveness throughout the page lifecycle.
|
|
102
|
+
|
|
103
|
+
**Optimization Strategies:**
|
|
104
|
+
|
|
105
|
+
#### 1. Reduce Long Tasks
|
|
106
|
+
```javascript
|
|
107
|
+
// BAD: Long blocking task
|
|
108
|
+
function processLargeDataset(data) {
|
|
109
|
+
data.forEach(item => heavyComputation(item));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// GOOD: Break into smaller tasks with yielding
|
|
113
|
+
async function processLargeDataset(data) {
|
|
114
|
+
for (let i = 0; i < data.length; i++) {
|
|
115
|
+
heavyComputation(data[i]);
|
|
116
|
+
|
|
117
|
+
// Yield to main thread every 50ms
|
|
118
|
+
if (i % 100 === 0) {
|
|
119
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// BETTER: Use scheduler.yield() (when available)
|
|
125
|
+
async function processLargeDataset(data) {
|
|
126
|
+
for (let i = 0; i < data.length; i++) {
|
|
127
|
+
heavyComputation(data[i]);
|
|
128
|
+
|
|
129
|
+
if (i % 100 === 0 && 'scheduler' in window) {
|
|
130
|
+
await scheduler.yield();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### 2. Debounce Input Handlers
|
|
137
|
+
```javascript
|
|
138
|
+
// Debounce expensive operations
|
|
139
|
+
function debounce(func, wait) {
|
|
140
|
+
let timeout;
|
|
141
|
+
return function executedFunction(...args) {
|
|
142
|
+
clearTimeout(timeout);
|
|
143
|
+
timeout = setTimeout(() => func(...args), wait);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Usage
|
|
148
|
+
const handleSearch = debounce((query) => {
|
|
149
|
+
fetchSearchResults(query);
|
|
150
|
+
}, 300);
|
|
151
|
+
|
|
152
|
+
input.addEventListener('input', (e) => handleSearch(e.target.value));
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### 3. Use Web Workers for Heavy Computations
|
|
156
|
+
```javascript
|
|
157
|
+
// main.js
|
|
158
|
+
const worker = new Worker('worker.js');
|
|
159
|
+
|
|
160
|
+
button.addEventListener('click', () => {
|
|
161
|
+
worker.postMessage({ data: largeDataset });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
worker.onmessage = (e) => {
|
|
165
|
+
updateUI(e.data.result);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// worker.js
|
|
169
|
+
self.onmessage = (e) => {
|
|
170
|
+
const result = heavyComputation(e.data);
|
|
171
|
+
self.postMessage({ result });
|
|
172
|
+
};
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### 4. Optimize React Event Handlers
|
|
176
|
+
```javascript
|
|
177
|
+
// BAD: Creating new function on every render
|
|
178
|
+
function SearchComponent() {
|
|
179
|
+
return <input onChange={(e) => handleChange(e.target.value)} />;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// GOOD: Memoized callback
|
|
183
|
+
function SearchComponent() {
|
|
184
|
+
const handleChange = useCallback((e) => {
|
|
185
|
+
// Handle change
|
|
186
|
+
}, []);
|
|
187
|
+
|
|
188
|
+
return <input onChange={handleChange} />;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### 5. CSS Containment for Faster Rendering
|
|
193
|
+
```css
|
|
194
|
+
/* Isolate rendering work to specific containers */
|
|
195
|
+
.card {
|
|
196
|
+
contain: layout style paint;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.list-item {
|
|
200
|
+
content-visibility: auto;
|
|
201
|
+
contain-intrinsic-size: 0 200px;
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### CLS (Cumulative Layout Shift)
|
|
206
|
+
|
|
207
|
+
**Target:** ≤0.1 (Good), 0.1-0.25 (Needs Improvement), >0.25 (Poor)
|
|
208
|
+
|
|
209
|
+
**What it Measures:** Visual stability - unexpected layout shifts during page load and user interaction.
|
|
210
|
+
|
|
211
|
+
**Optimization Strategies:**
|
|
212
|
+
|
|
213
|
+
#### 1. Reserve Space for Images and Embeds
|
|
214
|
+
```html
|
|
215
|
+
<!-- Always include width and height attributes -->
|
|
216
|
+
<img src="photo.jpg" alt="Photo" width="800" height="600">
|
|
217
|
+
|
|
218
|
+
<!-- CSS aspect ratio (modern) -->
|
|
219
|
+
<style>
|
|
220
|
+
.image-container {
|
|
221
|
+
aspect-ratio: 16 / 9;
|
|
222
|
+
width: 100%;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.image-container img {
|
|
226
|
+
width: 100%;
|
|
227
|
+
height: 100%;
|
|
228
|
+
object-fit: cover;
|
|
229
|
+
}
|
|
230
|
+
</style>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### 2. Reserve Space for Dynamic Content
|
|
234
|
+
```css
|
|
235
|
+
/* Reserve minimum height for dynamic content */
|
|
236
|
+
.dynamic-content {
|
|
237
|
+
min-height: 400px;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* Skeleton screens */
|
|
241
|
+
.skeleton {
|
|
242
|
+
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
|
243
|
+
background-size: 200% 100%;
|
|
244
|
+
animation: loading 1.5s infinite;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@keyframes loading {
|
|
248
|
+
0% { background-position: 200% 0; }
|
|
249
|
+
100% { background-position: -200% 0; }
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### 3. Avoid Inserting Content Above Existing Content
|
|
254
|
+
```javascript
|
|
255
|
+
// BAD: Prepending content causes shift
|
|
256
|
+
container.innerHTML = newContent + container.innerHTML;
|
|
257
|
+
|
|
258
|
+
// GOOD: Append or use proper animations
|
|
259
|
+
container.insertAdjacentHTML('beforeend', newContent);
|
|
260
|
+
|
|
261
|
+
// BETTER: Animate new content in
|
|
262
|
+
container.style.transform = 'translateY(100px)';
|
|
263
|
+
container.innerHTML = newContent;
|
|
264
|
+
requestAnimationFrame(() => {
|
|
265
|
+
container.style.transform = 'translateY(0)';
|
|
266
|
+
container.style.transition = 'transform 0.3s';
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### 4. Font Loading Optimization
|
|
271
|
+
```html
|
|
272
|
+
<!-- Preload critical fonts -->
|
|
273
|
+
<link rel="preload" as="font" type="font/woff2" href="/fonts/main.woff2" crossorigin>
|
|
274
|
+
|
|
275
|
+
<style>
|
|
276
|
+
/* font-display: swap prevents invisible text but may cause shift */
|
|
277
|
+
/* font-display: optional is better for CLS */
|
|
278
|
+
@font-face {
|
|
279
|
+
font-family: 'Main Font';
|
|
280
|
+
src: url('/fonts/main.woff2') format('woff2');
|
|
281
|
+
font-display: optional;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* Fallback font metrics matching */
|
|
285
|
+
body {
|
|
286
|
+
font-family: 'Main Font', -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
|
|
287
|
+
}
|
|
288
|
+
</style>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### 5. Ad and Embed Containers
|
|
292
|
+
```html
|
|
293
|
+
<!-- Reserve space for ads -->
|
|
294
|
+
<div class="ad-container" style="min-height: 250px;">
|
|
295
|
+
<!-- Ad loads here -->
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
<!-- YouTube embed with aspect ratio -->
|
|
299
|
+
<div style="aspect-ratio: 16/9; width: 100%;">
|
|
300
|
+
<iframe
|
|
301
|
+
src="https://www.youtube.com/embed/VIDEO_ID"
|
|
302
|
+
style="width: 100%; height: 100%;"
|
|
303
|
+
loading="lazy"
|
|
304
|
+
></iframe>
|
|
305
|
+
</div>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Supporting Metrics
|
|
309
|
+
|
|
310
|
+
#### FCP (First Contentful Paint)
|
|
311
|
+
|
|
312
|
+
**Target:** <1.8 seconds (Good), 1.8-3.0s (Needs Improvement), >3.0s (Poor)
|
|
313
|
+
|
|
314
|
+
**What it Measures:** Time until the first text or image is painted.
|
|
315
|
+
|
|
316
|
+
**Optimization:**
|
|
317
|
+
- Minimize render-blocking resources (CSS, JS)
|
|
318
|
+
- Use critical CSS inline in `<head>`
|
|
319
|
+
- Defer non-critical JavaScript
|
|
320
|
+
- Optimize server response times (TTFB)
|
|
321
|
+
|
|
322
|
+
```html
|
|
323
|
+
<!-- Inline critical CSS -->
|
|
324
|
+
<style>
|
|
325
|
+
/* Above-the-fold styles */
|
|
326
|
+
body { margin: 0; font-family: system-ui; }
|
|
327
|
+
.hero { height: 100vh; }
|
|
328
|
+
</style>
|
|
329
|
+
|
|
330
|
+
<!-- Defer non-critical CSS -->
|
|
331
|
+
<link rel="preload" as="style" href="main.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
332
|
+
<noscript><link rel="stylesheet" href="main.css"></noscript>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### TTFB (Time to First Byte)
|
|
336
|
+
|
|
337
|
+
**Target:** <800ms (Good), 800-1800ms (Needs Improvement), >1800ms (Poor)
|
|
338
|
+
|
|
339
|
+
**What it Measures:** Server response time.
|
|
340
|
+
|
|
341
|
+
**Optimization:**
|
|
342
|
+
- Use CDN edge caching
|
|
343
|
+
- Enable server-side caching (Redis, Memcached)
|
|
344
|
+
- Optimize database queries
|
|
345
|
+
- Enable HTTP/2 or HTTP/3
|
|
346
|
+
- Use 103 Early Hints
|
|
347
|
+
|
|
348
|
+
#### TBT (Total Blocking Time)
|
|
349
|
+
|
|
350
|
+
**Weight:** 30% (highest in Lighthouse v12)
|
|
351
|
+
|
|
352
|
+
**Target:** <200ms (Good), 200-600ms (Needs Improvement), >600ms (Poor)
|
|
353
|
+
|
|
354
|
+
**What it Measures:** Total time the main thread was blocked by long tasks (>50ms).
|
|
355
|
+
|
|
356
|
+
**Optimization:**
|
|
357
|
+
- Code splitting to reduce JavaScript execution time
|
|
358
|
+
- Defer non-critical JavaScript
|
|
359
|
+
- Break up long tasks
|
|
360
|
+
- Remove unused code
|
|
361
|
+
- Optimize third-party scripts
|
|
362
|
+
|
|
363
|
+
## Lighthouse Scoring (v12 - August 2024)
|
|
364
|
+
|
|
365
|
+
### Performance Score Weights
|
|
366
|
+
|
|
367
|
+
Lighthouse v12 (released August 2024) updated performance scoring weights:
|
|
368
|
+
|
|
369
|
+
- **Total Blocking Time (TBT):** 30% - HIGHEST WEIGHT
|
|
370
|
+
- **Largest Contentful Paint (LCP):** 25%
|
|
371
|
+
- **Cumulative Layout Shift (CLS):** 25%
|
|
372
|
+
- **First Contentful Paint (FCP):** 10%
|
|
373
|
+
- **Speed Index:** 10%
|
|
374
|
+
|
|
375
|
+
### Scoring Calculation
|
|
376
|
+
|
|
377
|
+
Each metric has a curve that maps raw values to scores (0-100):
|
|
378
|
+
|
|
379
|
+
```javascript
|
|
380
|
+
// Example scoring curve (simplified)
|
|
381
|
+
function calculateScore(metrics) {
|
|
382
|
+
const weights = {
|
|
383
|
+
TBT: 0.30,
|
|
384
|
+
LCP: 0.25,
|
|
385
|
+
CLS: 0.25,
|
|
386
|
+
FCP: 0.10,
|
|
387
|
+
SI: 0.10
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// Each metric is scored on a curve
|
|
391
|
+
const scores = {
|
|
392
|
+
TBT: scoreFromValue(metrics.TBT, TBT_CURVE),
|
|
393
|
+
LCP: scoreFromValue(metrics.LCP, LCP_CURVE),
|
|
394
|
+
CLS: scoreFromValue(metrics.CLS, CLS_CURVE),
|
|
395
|
+
FCP: scoreFromValue(metrics.FCP, FCP_CURVE),
|
|
396
|
+
SI: scoreFromValue(metrics.SI, SI_CURVE)
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// Weighted average
|
|
400
|
+
return Object.keys(weights).reduce((total, metric) => {
|
|
401
|
+
return total + (scores[metric] * weights[metric]);
|
|
402
|
+
}, 0);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Running Lighthouse
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
# CLI audit
|
|
410
|
+
lighthouse https://example.com --view --output html --output-path ./report.html
|
|
411
|
+
|
|
412
|
+
# With specific categories
|
|
413
|
+
lighthouse https://example.com --only-categories=performance
|
|
414
|
+
|
|
415
|
+
# Mobile simulation (default)
|
|
416
|
+
lighthouse https://example.com --preset=mobile
|
|
417
|
+
|
|
418
|
+
# Desktop audit
|
|
419
|
+
lighthouse https://example.com --preset=desktop
|
|
420
|
+
|
|
421
|
+
# CI/CD integration
|
|
422
|
+
lighthouse https://example.com --output=json --output-path=./lighthouse.json
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Image Optimization
|
|
426
|
+
|
|
427
|
+
Images account for 73% of LCP elements and are the largest contributor to page weight. Modern image optimization is essential for performance.
|
|
428
|
+
|
|
429
|
+
### Modern Formats
|
|
430
|
+
|
|
431
|
+
**WebP/AVIF:** 96% browser support (2024)
|
|
432
|
+
|
|
433
|
+
```html
|
|
434
|
+
<!-- Progressive enhancement with multiple formats -->
|
|
435
|
+
<picture>
|
|
436
|
+
<source srcset="image.avif" type="image/avif">
|
|
437
|
+
<source srcset="image.webp" type="image/webp">
|
|
438
|
+
<img src="image.jpg" alt="Description" width="800" height="600">
|
|
439
|
+
</picture>
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
**Format Comparison:**
|
|
443
|
+
- AVIF: 50% smaller than JPEG, excellent quality
|
|
444
|
+
- WebP: 30% smaller than JPEG, great browser support
|
|
445
|
+
- JPEG: Universal support, baseline format
|
|
446
|
+
|
|
447
|
+
### Responsive Images
|
|
448
|
+
|
|
449
|
+
```html
|
|
450
|
+
<!-- Density descriptors for different screen densities -->
|
|
451
|
+
<img
|
|
452
|
+
srcset="image-1x.jpg 1x, image-2x.jpg 2x, image-3x.jpg 3x"
|
|
453
|
+
src="image-1x.jpg"
|
|
454
|
+
alt="Description"
|
|
455
|
+
>
|
|
456
|
+
|
|
457
|
+
<!-- Width descriptors with sizes attribute -->
|
|
458
|
+
<img
|
|
459
|
+
srcset="
|
|
460
|
+
image-400.jpg 400w,
|
|
461
|
+
image-800.jpg 800w,
|
|
462
|
+
image-1200.jpg 1200w,
|
|
463
|
+
image-1600.jpg 1600w
|
|
464
|
+
"
|
|
465
|
+
sizes="
|
|
466
|
+
(max-width: 600px) 100vw,
|
|
467
|
+
(max-width: 1200px) 50vw,
|
|
468
|
+
800px
|
|
469
|
+
"
|
|
470
|
+
src="image-800.jpg"
|
|
471
|
+
alt="Description"
|
|
472
|
+
>
|
|
473
|
+
|
|
474
|
+
<!-- Art direction with picture element -->
|
|
475
|
+
<picture>
|
|
476
|
+
<source media="(min-width: 1200px)" srcset="hero-wide.jpg">
|
|
477
|
+
<source media="(min-width: 600px)" srcset="hero-medium.jpg">
|
|
478
|
+
<img src="hero-small.jpg" alt="Hero image">
|
|
479
|
+
</picture>
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Lazy Loading
|
|
483
|
+
|
|
484
|
+
```html
|
|
485
|
+
<!-- Native lazy loading (DO NOT use on LCP images) -->
|
|
486
|
+
<img src="image.jpg" loading="lazy" alt="Below fold image">
|
|
487
|
+
|
|
488
|
+
<!-- Eager loading for above-fold images -->
|
|
489
|
+
<img src="hero.jpg" loading="eager" fetchpriority="high" alt="Hero">
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**JavaScript lazy loading with Intersection Observer:**
|
|
493
|
+
|
|
494
|
+
```javascript
|
|
495
|
+
const imageObserver = new IntersectionObserver((entries, observer) => {
|
|
496
|
+
entries.forEach(entry => {
|
|
497
|
+
if (entry.isIntersecting) {
|
|
498
|
+
const img = entry.target;
|
|
499
|
+
img.src = img.dataset.src;
|
|
500
|
+
img.classList.remove('lazy');
|
|
501
|
+
observer.unobserve(img);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
document.querySelectorAll('img.lazy').forEach(img => {
|
|
507
|
+
imageObserver.observe(img);
|
|
508
|
+
});
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Image CDN Strategies
|
|
512
|
+
|
|
513
|
+
**Cloudinary:**
|
|
514
|
+
```javascript
|
|
515
|
+
// Automatic format and quality optimization
|
|
516
|
+
const url = cloudinary.url('sample.jpg', {
|
|
517
|
+
fetch_format: 'auto',
|
|
518
|
+
quality: 'auto',
|
|
519
|
+
width: 800,
|
|
520
|
+
crop: 'scale',
|
|
521
|
+
dpr: 'auto' // Automatically handle device pixel ratio
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**Imgix:**
|
|
526
|
+
```javascript
|
|
527
|
+
// Dynamic image transformation
|
|
528
|
+
const imgixUrl = `https://domain.imgix.net/image.jpg?w=800&auto=format,compress&fit=crop`;
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
**Next.js Image Component:**
|
|
532
|
+
```jsx
|
|
533
|
+
import Image from 'next/image';
|
|
534
|
+
|
|
535
|
+
// Automatic optimization, lazy loading, and responsive images
|
|
536
|
+
<Image
|
|
537
|
+
src="/hero.jpg"
|
|
538
|
+
alt="Hero image"
|
|
539
|
+
width={1200}
|
|
540
|
+
height={600}
|
|
541
|
+
priority // Disable lazy loading for LCP image
|
|
542
|
+
quality={85}
|
|
543
|
+
/>
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
## JavaScript Optimization
|
|
547
|
+
|
|
548
|
+
JavaScript is often the primary bottleneck for TBT and INP. Optimizing JavaScript delivery and execution is critical.
|
|
549
|
+
|
|
550
|
+
### Code Splitting
|
|
551
|
+
|
|
552
|
+
**Impact:** 40-60% bundle size reduction
|
|
553
|
+
|
|
554
|
+
```javascript
|
|
555
|
+
// React lazy loading
|
|
556
|
+
import React, { lazy, Suspense } from 'react';
|
|
557
|
+
|
|
558
|
+
const HeavyComponent = lazy(() => import('./HeavyComponent'));
|
|
559
|
+
|
|
560
|
+
function App() {
|
|
561
|
+
return (
|
|
562
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
563
|
+
<HeavyComponent />
|
|
564
|
+
</Suspense>
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Route-based code splitting
|
|
569
|
+
import { lazy } from 'react';
|
|
570
|
+
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
|
571
|
+
|
|
572
|
+
const Home = lazy(() => import('./pages/Home'));
|
|
573
|
+
const Dashboard = lazy(() => import('./pages/Dashboard'));
|
|
574
|
+
const Settings = lazy(() => import('./pages/Settings'));
|
|
575
|
+
|
|
576
|
+
function App() {
|
|
577
|
+
return (
|
|
578
|
+
<BrowserRouter>
|
|
579
|
+
<Suspense fallback={<LoadingSpinner />}>
|
|
580
|
+
<Routes>
|
|
581
|
+
<Route path="/" element={<Home />} />
|
|
582
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
583
|
+
<Route path="/settings" element={<Settings />} />
|
|
584
|
+
</Routes>
|
|
585
|
+
</Suspense>
|
|
586
|
+
</BrowserRouter>
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**Webpack configuration:**
|
|
592
|
+
```javascript
|
|
593
|
+
// webpack.config.js
|
|
594
|
+
module.exports = {
|
|
595
|
+
optimization: {
|
|
596
|
+
splitChunks: {
|
|
597
|
+
chunks: 'all',
|
|
598
|
+
cacheGroups: {
|
|
599
|
+
vendor: {
|
|
600
|
+
test: /[\\/]node_modules[\\/]/,
|
|
601
|
+
name: 'vendors',
|
|
602
|
+
priority: 10
|
|
603
|
+
},
|
|
604
|
+
common: {
|
|
605
|
+
minChunks: 2,
|
|
606
|
+
name: 'common',
|
|
607
|
+
priority: 5
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### Tree Shaking
|
|
616
|
+
|
|
617
|
+
Remove unused code from bundles:
|
|
618
|
+
|
|
619
|
+
```javascript
|
|
620
|
+
// package.json
|
|
621
|
+
{
|
|
622
|
+
"sideEffects": false // Enable tree shaking
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Or specify files with side effects
|
|
626
|
+
{
|
|
627
|
+
"sideEffects": ["*.css", "*.scss"]
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Import only what you need
|
|
631
|
+
import { debounce } from 'lodash-es'; // Good: ES modules
|
|
632
|
+
import debounce from 'lodash/debounce'; // Good: Individual module
|
|
633
|
+
|
|
634
|
+
import _ from 'lodash'; // Bad: Imports entire library
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
**Vite tree shaking (automatic):**
|
|
638
|
+
```javascript
|
|
639
|
+
// vite.config.js
|
|
640
|
+
export default {
|
|
641
|
+
build: {
|
|
642
|
+
minify: 'terser',
|
|
643
|
+
terserOptions: {
|
|
644
|
+
compress: {
|
|
645
|
+
drop_console: true,
|
|
646
|
+
drop_debugger: true,
|
|
647
|
+
pure_funcs: ['console.log']
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Lazy Loading Modules
|
|
655
|
+
|
|
656
|
+
```javascript
|
|
657
|
+
// Dynamic imports
|
|
658
|
+
button.addEventListener('click', async () => {
|
|
659
|
+
const { default: Chart } = await import('chart.js');
|
|
660
|
+
const chart = new Chart(ctx, config);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
// Preload for faster loading
|
|
664
|
+
const ChartPromise = import(/* webpackPrefetch: true */ 'chart.js');
|
|
665
|
+
|
|
666
|
+
button.addEventListener('click', async () => {
|
|
667
|
+
const { default: Chart } = await ChartPromise;
|
|
668
|
+
const chart = new Chart(ctx, config);
|
|
669
|
+
});
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### Bundle Analysis
|
|
673
|
+
|
|
674
|
+
```bash
|
|
675
|
+
# webpack-bundle-analyzer
|
|
676
|
+
npm install --save-dev webpack-bundle-analyzer
|
|
677
|
+
|
|
678
|
+
# Add to webpack config
|
|
679
|
+
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
|
680
|
+
|
|
681
|
+
module.exports = {
|
|
682
|
+
plugins: [
|
|
683
|
+
new BundleAnalyzerPlugin()
|
|
684
|
+
]
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
# Run build
|
|
688
|
+
npm run build
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
**Vite bundle analysis:**
|
|
692
|
+
```bash
|
|
693
|
+
# Install rollup-plugin-visualizer
|
|
694
|
+
npm install --save-dev rollup-plugin-visualizer
|
|
695
|
+
|
|
696
|
+
# vite.config.js
|
|
697
|
+
import { visualizer } from 'rollup-plugin-visualizer';
|
|
698
|
+
|
|
699
|
+
export default {
|
|
700
|
+
plugins: [
|
|
701
|
+
visualizer({
|
|
702
|
+
open: true,
|
|
703
|
+
gzipSize: true,
|
|
704
|
+
brotliSize: true
|
|
705
|
+
})
|
|
706
|
+
]
|
|
707
|
+
};
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### Third-Party Scripts
|
|
711
|
+
|
|
712
|
+
**Problem:** 90%+ sites affected by third-party bloat
|
|
713
|
+
|
|
714
|
+
**Strategies:**
|
|
715
|
+
|
|
716
|
+
```html
|
|
717
|
+
<!-- 1. Defer non-critical scripts -->
|
|
718
|
+
<script src="analytics.js" defer></script>
|
|
719
|
+
|
|
720
|
+
<!-- 2. Use async for independent scripts -->
|
|
721
|
+
<script src="tracking.js" async></script>
|
|
722
|
+
|
|
723
|
+
<!-- 3. Lazy load based on user interaction -->
|
|
724
|
+
<script>
|
|
725
|
+
let analyticsLoaded = false;
|
|
726
|
+
|
|
727
|
+
function loadAnalytics() {
|
|
728
|
+
if (analyticsLoaded) return;
|
|
729
|
+
analyticsLoaded = true;
|
|
730
|
+
|
|
731
|
+
const script = document.createElement('script');
|
|
732
|
+
script.src = 'https://analytics.example.com/script.js';
|
|
733
|
+
document.head.appendChild(script);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Load on first interaction
|
|
737
|
+
['click', 'scroll', 'keydown'].forEach(event => {
|
|
738
|
+
window.addEventListener(event, loadAnalytics, { once: true });
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// Fallback: load after 5 seconds
|
|
742
|
+
setTimeout(loadAnalytics, 5000);
|
|
743
|
+
</script>
|
|
744
|
+
|
|
745
|
+
<!-- 4. Use facade pattern for heavy embeds -->
|
|
746
|
+
<div class="youtube-facade" data-video-id="VIDEO_ID">
|
|
747
|
+
<button class="play-button">Play Video</button>
|
|
748
|
+
</div>
|
|
749
|
+
|
|
750
|
+
<script>
|
|
751
|
+
document.querySelectorAll('.youtube-facade').forEach(facade => {
|
|
752
|
+
facade.addEventListener('click', function() {
|
|
753
|
+
const iframe = document.createElement('iframe');
|
|
754
|
+
iframe.src = `https://www.youtube.com/embed/${this.dataset.videoId}?autoplay=1`;
|
|
755
|
+
iframe.allow = 'autoplay';
|
|
756
|
+
this.replaceWith(iframe);
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
</script>
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
**Partytown for web workers:**
|
|
763
|
+
```html
|
|
764
|
+
<!-- Move third-party scripts to web worker -->
|
|
765
|
+
<script type="text/partytown" src="analytics.js"></script>
|
|
766
|
+
<script type="text/partytown">
|
|
767
|
+
// This runs in a web worker
|
|
768
|
+
gtag('config', 'GA_MEASUREMENT_ID');
|
|
769
|
+
</script>
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
## CSS Optimization
|
|
773
|
+
|
|
774
|
+
### Critical CSS Extraction
|
|
775
|
+
|
|
776
|
+
Extract and inline above-the-fold CSS:
|
|
777
|
+
|
|
778
|
+
```bash
|
|
779
|
+
# Using Critical package
|
|
780
|
+
npm install --save-dev critical
|
|
781
|
+
|
|
782
|
+
# Generate critical CSS
|
|
783
|
+
npx critical https://example.com --inline --minify
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
```javascript
|
|
787
|
+
// Build script with critical CSS
|
|
788
|
+
const critical = require('critical');
|
|
789
|
+
|
|
790
|
+
critical.generate({
|
|
791
|
+
inline: true,
|
|
792
|
+
base: 'dist/',
|
|
793
|
+
src: 'index.html',
|
|
794
|
+
target: {
|
|
795
|
+
html: 'index-critical.html',
|
|
796
|
+
css: 'critical.css'
|
|
797
|
+
},
|
|
798
|
+
dimensions: [
|
|
799
|
+
{ width: 375, height: 667 }, // Mobile
|
|
800
|
+
{ width: 1920, height: 1080 } // Desktop
|
|
801
|
+
]
|
|
802
|
+
});
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
### Unused CSS Removal
|
|
806
|
+
|
|
807
|
+
**Impact:** 80-90% reduction with PurgeCSS
|
|
808
|
+
|
|
809
|
+
```javascript
|
|
810
|
+
// PostCSS with PurgeCSS
|
|
811
|
+
// postcss.config.js
|
|
812
|
+
module.exports = {
|
|
813
|
+
plugins: [
|
|
814
|
+
require('@fullhuman/postcss-purgecss')({
|
|
815
|
+
content: ['./src/**/*.html', './src/**/*.js', './src/**/*.jsx'],
|
|
816
|
+
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || [],
|
|
817
|
+
safelist: ['active', 'disabled', /^data-/] // Preserve dynamic classes
|
|
818
|
+
})
|
|
819
|
+
]
|
|
820
|
+
};
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
**Tailwind CSS automatic purging:**
|
|
824
|
+
```javascript
|
|
825
|
+
// tailwind.config.js
|
|
826
|
+
module.exports = {
|
|
827
|
+
content: [
|
|
828
|
+
'./src/**/*.{js,jsx,ts,tsx,html}',
|
|
829
|
+
'./public/index.html'
|
|
830
|
+
],
|
|
831
|
+
theme: {
|
|
832
|
+
extend: {}
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
### Modern CSS Features
|
|
838
|
+
|
|
839
|
+
```css
|
|
840
|
+
/* Container queries for responsive components */
|
|
841
|
+
.card-container {
|
|
842
|
+
container-type: inline-size;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
@container (min-width: 400px) {
|
|
846
|
+
.card {
|
|
847
|
+
display: grid;
|
|
848
|
+
grid-template-columns: 1fr 2fr;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/* :has() for parent selection */
|
|
853
|
+
.article:has(img) {
|
|
854
|
+
display: grid;
|
|
855
|
+
grid-template-columns: 1fr 1fr;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/* CSS containment for performance */
|
|
859
|
+
.card {
|
|
860
|
+
contain: layout style paint;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
.list-item {
|
|
864
|
+
content-visibility: auto;
|
|
865
|
+
contain-intrinsic-size: 0 200px;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/* Native CSS nesting */
|
|
869
|
+
.component {
|
|
870
|
+
padding: 1rem;
|
|
871
|
+
|
|
872
|
+
& .title {
|
|
873
|
+
font-size: 1.5rem;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
&:hover {
|
|
877
|
+
background: #f0f0f0;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
## Resource Loading Optimization
|
|
883
|
+
|
|
884
|
+
### Resource Hints
|
|
885
|
+
|
|
886
|
+
```html
|
|
887
|
+
<!-- Preconnect: Establish early connections to critical third-party origins -->
|
|
888
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
889
|
+
<link rel="preconnect" href="https://cdn.example.com">
|
|
890
|
+
|
|
891
|
+
<!-- DNS-prefetch: Resolve DNS only (lighter than preconnect) -->
|
|
892
|
+
<link rel="dns-prefetch" href="https://analytics.example.com">
|
|
893
|
+
|
|
894
|
+
<!-- Preload: Fetch critical resources early -->
|
|
895
|
+
<link rel="preload" as="image" href="hero.webp" fetchpriority="high">
|
|
896
|
+
<link rel="preload" as="font" type="font/woff2" href="main.woff2" crossorigin>
|
|
897
|
+
<link rel="preload" as="script" href="critical.js">
|
|
898
|
+
<link rel="preload" as="style" href="critical.css">
|
|
899
|
+
|
|
900
|
+
<!-- Prefetch: Fetch resources for next navigation (low priority) -->
|
|
901
|
+
<link rel="prefetch" as="document" href="/next-page.html">
|
|
902
|
+
<link rel="prefetch" as="script" href="next-page.js">
|
|
903
|
+
|
|
904
|
+
<!-- Modulepreload: Preload ES modules -->
|
|
905
|
+
<link rel="modulepreload" href="app.js">
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
### Priority Hints API
|
|
909
|
+
|
|
910
|
+
```html
|
|
911
|
+
<!-- High priority for critical images -->
|
|
912
|
+
<img src="hero.jpg" fetchpriority="high" alt="Hero">
|
|
913
|
+
|
|
914
|
+
<!-- Low priority for below-fold images -->
|
|
915
|
+
<img src="footer-logo.jpg" fetchpriority="low" alt="Logo">
|
|
916
|
+
|
|
917
|
+
<!-- Script prioritization -->
|
|
918
|
+
<script src="critical.js" fetchpriority="high"></script>
|
|
919
|
+
<script src="analytics.js" fetchpriority="low" async></script>
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
### 103 Early Hints
|
|
923
|
+
|
|
924
|
+
**Impact:** 25-37% improvement in page load times
|
|
925
|
+
|
|
926
|
+
**Server configuration (Nginx):**
|
|
927
|
+
```nginx
|
|
928
|
+
location / {
|
|
929
|
+
add_header Link "</css/critical.css>; rel=preload; as=style" always;
|
|
930
|
+
add_header Link "</js/main.js>; rel=preload; as=script" always;
|
|
931
|
+
add_header Link "</fonts/main.woff2>; rel=preload; as=font; crossorigin" always;
|
|
932
|
+
|
|
933
|
+
# Return 103 Early Hints
|
|
934
|
+
return 103;
|
|
935
|
+
}
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
**Node.js/Express:**
|
|
939
|
+
```javascript
|
|
940
|
+
app.use((req, res, next) => {
|
|
941
|
+
res.writeProcessing(); // 103 Early Hints
|
|
942
|
+
res.setHeader('Link', [
|
|
943
|
+
'</css/critical.css>; rel=preload; as=style',
|
|
944
|
+
'</js/main.js>; rel=preload; as=script',
|
|
945
|
+
'</fonts/main.woff2>; rel=preload; as=font; crossorigin'
|
|
946
|
+
].join(', '));
|
|
947
|
+
next();
|
|
948
|
+
});
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
### HTTP/2 and HTTP/3
|
|
952
|
+
|
|
953
|
+
**Benefits:**
|
|
954
|
+
- Multiplexing: Multiple requests over single connection
|
|
955
|
+
- Header compression (HPACK)
|
|
956
|
+
- Server push (HTTP/2)
|
|
957
|
+
- Improved reliability over UDP (HTTP/3 with QUIC)
|
|
958
|
+
|
|
959
|
+
**Enable in Nginx:**
|
|
960
|
+
```nginx
|
|
961
|
+
server {
|
|
962
|
+
listen 443 ssl http2;
|
|
963
|
+
listen 443 http3 reuseport; # HTTP/3
|
|
964
|
+
|
|
965
|
+
add_header Alt-Svc 'h3=":443"; ma=86400'; # Advertise HTTP/3
|
|
966
|
+
|
|
967
|
+
ssl_certificate /path/to/cert.pem;
|
|
968
|
+
ssl_certificate_key /path/to/key.pem;
|
|
969
|
+
ssl_protocols TLSv1.2 TLSv1.3;
|
|
970
|
+
}
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
## Caching Strategies
|
|
974
|
+
|
|
975
|
+
### Cache-Control Headers
|
|
976
|
+
|
|
977
|
+
```nginx
|
|
978
|
+
# Nginx configuration
|
|
979
|
+
location ~* \.(jpg|jpeg|png|gif|webp|avif)$ {
|
|
980
|
+
expires 1y;
|
|
981
|
+
add_header Cache-Control "public, immutable";
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
location ~* \.(css|js)$ {
|
|
985
|
+
expires 1y;
|
|
986
|
+
add_header Cache-Control "public, immutable";
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
location ~* \.(html)$ {
|
|
990
|
+
expires -1;
|
|
991
|
+
add_header Cache-Control "no-cache, must-revalidate";
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
# Stale-while-revalidate for API responses
|
|
995
|
+
location /api/ {
|
|
996
|
+
add_header Cache-Control "max-age=60, stale-while-revalidate=600";
|
|
997
|
+
}
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
**Express.js:**
|
|
1001
|
+
```javascript
|
|
1002
|
+
// Static assets with long cache
|
|
1003
|
+
app.use('/static', express.static('public', {
|
|
1004
|
+
maxAge: '1y',
|
|
1005
|
+
immutable: true
|
|
1006
|
+
}));
|
|
1007
|
+
|
|
1008
|
+
// API with stale-while-revalidate
|
|
1009
|
+
app.get('/api/data', (req, res) => {
|
|
1010
|
+
res.set('Cache-Control', 'max-age=60, stale-while-revalidate=600');
|
|
1011
|
+
res.json(data);
|
|
1012
|
+
});
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
### Service Workers
|
|
1016
|
+
|
|
1017
|
+
```javascript
|
|
1018
|
+
// sw.js - Service worker with Workbox
|
|
1019
|
+
import { precacheAndRoute } from 'workbox-precaching';
|
|
1020
|
+
import { registerRoute } from 'workbox-routing';
|
|
1021
|
+
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
|
|
1022
|
+
import { ExpirationPlugin } from 'workbox-expiration';
|
|
1023
|
+
|
|
1024
|
+
// Precache static assets
|
|
1025
|
+
precacheAndRoute(self.__WB_MANIFEST);
|
|
1026
|
+
|
|
1027
|
+
// Cache images with CacheFirst strategy
|
|
1028
|
+
registerRoute(
|
|
1029
|
+
({ request }) => request.destination === 'image',
|
|
1030
|
+
new CacheFirst({
|
|
1031
|
+
cacheName: 'images',
|
|
1032
|
+
plugins: [
|
|
1033
|
+
new ExpirationPlugin({
|
|
1034
|
+
maxEntries: 60,
|
|
1035
|
+
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
|
|
1036
|
+
}),
|
|
1037
|
+
],
|
|
1038
|
+
})
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1041
|
+
// Cache API requests with NetworkFirst
|
|
1042
|
+
registerRoute(
|
|
1043
|
+
({ url }) => url.pathname.startsWith('/api/'),
|
|
1044
|
+
new NetworkFirst({
|
|
1045
|
+
cacheName: 'api-cache',
|
|
1046
|
+
plugins: [
|
|
1047
|
+
new ExpirationPlugin({
|
|
1048
|
+
maxEntries: 50,
|
|
1049
|
+
maxAgeSeconds: 5 * 60, // 5 minutes
|
|
1050
|
+
}),
|
|
1051
|
+
],
|
|
1052
|
+
})
|
|
1053
|
+
);
|
|
1054
|
+
|
|
1055
|
+
// Cache CSS/JS with StaleWhileRevalidate
|
|
1056
|
+
registerRoute(
|
|
1057
|
+
({ request }) => request.destination === 'script' || request.destination === 'style',
|
|
1058
|
+
new StaleWhileRevalidate({
|
|
1059
|
+
cacheName: 'static-resources',
|
|
1060
|
+
})
|
|
1061
|
+
);
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
### CDN Edge Caching
|
|
1065
|
+
|
|
1066
|
+
**Cloudflare configuration:**
|
|
1067
|
+
```javascript
|
|
1068
|
+
// Cloudflare Worker for edge caching
|
|
1069
|
+
addEventListener('fetch', event => {
|
|
1070
|
+
event.respondWith(handleRequest(event.request));
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
async function handleRequest(request) {
|
|
1074
|
+
const cache = caches.default;
|
|
1075
|
+
let response = await cache.match(request);
|
|
1076
|
+
|
|
1077
|
+
if (!response) {
|
|
1078
|
+
response = await fetch(request);
|
|
1079
|
+
|
|
1080
|
+
// Cache for 1 hour if successful
|
|
1081
|
+
if (response.ok) {
|
|
1082
|
+
const headers = new Headers(response.headers);
|
|
1083
|
+
headers.set('Cache-Control', 'public, max-age=3600');
|
|
1084
|
+
response = new Response(response.body, {
|
|
1085
|
+
status: response.status,
|
|
1086
|
+
statusText: response.statusText,
|
|
1087
|
+
headers
|
|
1088
|
+
});
|
|
1089
|
+
event.waitUntil(cache.put(request, response.clone()));
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
return response;
|
|
1094
|
+
}
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
## Framework-Specific Optimizations
|
|
1098
|
+
|
|
1099
|
+
### Next.js
|
|
1100
|
+
|
|
1101
|
+
```jsx
|
|
1102
|
+
// next.config.js
|
|
1103
|
+
module.exports = {
|
|
1104
|
+
// Image optimization
|
|
1105
|
+
images: {
|
|
1106
|
+
formats: ['image/avif', 'image/webp'],
|
|
1107
|
+
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
|
1108
|
+
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
|
1109
|
+
},
|
|
1110
|
+
|
|
1111
|
+
// Font optimization
|
|
1112
|
+
optimizeFonts: true,
|
|
1113
|
+
|
|
1114
|
+
// Script optimization
|
|
1115
|
+
swcMinify: true,
|
|
1116
|
+
|
|
1117
|
+
// Compression
|
|
1118
|
+
compress: true,
|
|
1119
|
+
|
|
1120
|
+
// Generate static pages
|
|
1121
|
+
output: 'standalone',
|
|
1122
|
+
};
|
|
1123
|
+
|
|
1124
|
+
// Using Next.js Image component
|
|
1125
|
+
import Image from 'next/image';
|
|
1126
|
+
|
|
1127
|
+
export default function Hero() {
|
|
1128
|
+
return (
|
|
1129
|
+
<Image
|
|
1130
|
+
src="/hero.jpg"
|
|
1131
|
+
alt="Hero image"
|
|
1132
|
+
width={1200}
|
|
1133
|
+
height={600}
|
|
1134
|
+
priority // Preload LCP image
|
|
1135
|
+
quality={85}
|
|
1136
|
+
/>
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// Font optimization with next/font
|
|
1141
|
+
import { Inter } from 'next/font/google';
|
|
1142
|
+
|
|
1143
|
+
const inter = Inter({
|
|
1144
|
+
subsets: ['latin'],
|
|
1145
|
+
display: 'swap',
|
|
1146
|
+
variable: '--font-inter'
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
export default function RootLayout({ children }) {
|
|
1150
|
+
return (
|
|
1151
|
+
<html lang="en" className={inter.variable}>
|
|
1152
|
+
<body>{children}</body>
|
|
1153
|
+
</html>
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Static generation for performance
|
|
1158
|
+
export async function generateStaticParams() {
|
|
1159
|
+
const posts = await getPosts();
|
|
1160
|
+
return posts.map(post => ({ slug: post.slug }));
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Streaming SSR with Suspense
|
|
1164
|
+
import { Suspense } from 'react';
|
|
1165
|
+
|
|
1166
|
+
export default function Page() {
|
|
1167
|
+
return (
|
|
1168
|
+
<>
|
|
1169
|
+
<Header />
|
|
1170
|
+
<Suspense fallback={<Skeleton />}>
|
|
1171
|
+
<DynamicContent />
|
|
1172
|
+
</Suspense>
|
|
1173
|
+
<Footer />
|
|
1174
|
+
</>
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
### React
|
|
1180
|
+
|
|
1181
|
+
```jsx
|
|
1182
|
+
// Component memoization
|
|
1183
|
+
import { memo, useMemo, useCallback } from 'react';
|
|
1184
|
+
|
|
1185
|
+
const ExpensiveComponent = memo(({ data }) => {
|
|
1186
|
+
const processedData = useMemo(() => {
|
|
1187
|
+
return data.map(item => expensiveOperation(item));
|
|
1188
|
+
}, [data]);
|
|
1189
|
+
|
|
1190
|
+
const handleClick = useCallback(() => {
|
|
1191
|
+
// Handle click
|
|
1192
|
+
}, []);
|
|
1193
|
+
|
|
1194
|
+
return <div onClick={handleClick}>{processedData}</div>;
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
// Code splitting with React.lazy
|
|
1198
|
+
import { lazy, Suspense } from 'react';
|
|
1199
|
+
|
|
1200
|
+
const Dashboard = lazy(() => import('./Dashboard'));
|
|
1201
|
+
const Settings = lazy(() => import('./Settings'));
|
|
1202
|
+
|
|
1203
|
+
function App() {
|
|
1204
|
+
return (
|
|
1205
|
+
<Suspense fallback={<LoadingSpinner />}>
|
|
1206
|
+
<Routes>
|
|
1207
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
1208
|
+
<Route path="/settings" element={<Settings />} />
|
|
1209
|
+
</Routes>
|
|
1210
|
+
</Suspense>
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// Virtual scrolling for large lists
|
|
1215
|
+
import { FixedSizeList } from 'react-window';
|
|
1216
|
+
|
|
1217
|
+
function VirtualList({ items }) {
|
|
1218
|
+
return (
|
|
1219
|
+
<FixedSizeList
|
|
1220
|
+
height={600}
|
|
1221
|
+
itemCount={items.length}
|
|
1222
|
+
itemSize={80}
|
|
1223
|
+
width="100%"
|
|
1224
|
+
>
|
|
1225
|
+
{({ index, style }) => (
|
|
1226
|
+
<div style={style}>{items[index].name}</div>
|
|
1227
|
+
)}
|
|
1228
|
+
</FixedSizeList>
|
|
1229
|
+
);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// React 18 transitions for better INP
|
|
1233
|
+
import { useTransition } from 'react';
|
|
1234
|
+
|
|
1235
|
+
function SearchComponent() {
|
|
1236
|
+
const [isPending, startTransition] = useTransition();
|
|
1237
|
+
const [query, setQuery] = useState('');
|
|
1238
|
+
|
|
1239
|
+
const handleSearch = (e) => {
|
|
1240
|
+
const value = e.target.value;
|
|
1241
|
+
setQuery(value);
|
|
1242
|
+
|
|
1243
|
+
startTransition(() => {
|
|
1244
|
+
// This update is marked as non-urgent
|
|
1245
|
+
setSearchResults(performExpensiveSearch(value));
|
|
1246
|
+
});
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
return (
|
|
1250
|
+
<>
|
|
1251
|
+
<input value={query} onChange={handleSearch} />
|
|
1252
|
+
{isPending && <Spinner />}
|
|
1253
|
+
<Results />
|
|
1254
|
+
</>
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
```
|
|
1258
|
+
|
|
1259
|
+
### Vue
|
|
1260
|
+
|
|
1261
|
+
```vue
|
|
1262
|
+
<!-- Async components -->
|
|
1263
|
+
<script>
|
|
1264
|
+
import { defineAsyncComponent } from 'vue';
|
|
1265
|
+
|
|
1266
|
+
export default {
|
|
1267
|
+
components: {
|
|
1268
|
+
HeavyComponent: defineAsyncComponent(() =>
|
|
1269
|
+
import('./HeavyComponent.vue')
|
|
1270
|
+
)
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
</script>
|
|
1274
|
+
|
|
1275
|
+
<!-- Keep-alive for component caching -->
|
|
1276
|
+
<template>
|
|
1277
|
+
<keep-alive :max="10">
|
|
1278
|
+
<component :is="currentView" />
|
|
1279
|
+
</keep-alive>
|
|
1280
|
+
</template>
|
|
1281
|
+
|
|
1282
|
+
<!-- Virtual scrolling -->
|
|
1283
|
+
<script setup>
|
|
1284
|
+
import { VirtualScroller } from 'vue-virtual-scroller';
|
|
1285
|
+
|
|
1286
|
+
const items = ref([/* thousands of items */]);
|
|
1287
|
+
</script>
|
|
1288
|
+
|
|
1289
|
+
<template>
|
|
1290
|
+
<VirtualScroller
|
|
1291
|
+
:items="items"
|
|
1292
|
+
:item-height="80"
|
|
1293
|
+
key-field="id"
|
|
1294
|
+
>
|
|
1295
|
+
<template #default="{ item }">
|
|
1296
|
+
<div>{{ item.name }}</div>
|
|
1297
|
+
</template>
|
|
1298
|
+
</VirtualScroller>
|
|
1299
|
+
</template>
|
|
1300
|
+
|
|
1301
|
+
<!-- Vue 3 performance optimizations -->
|
|
1302
|
+
<script setup>
|
|
1303
|
+
import { computed, watchEffect } from 'vue';
|
|
1304
|
+
|
|
1305
|
+
// Computed values are cached
|
|
1306
|
+
const filteredItems = computed(() => {
|
|
1307
|
+
return items.value.filter(item => item.active);
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
// Use v-memo for expensive renders
|
|
1311
|
+
</script>
|
|
1312
|
+
|
|
1313
|
+
<template>
|
|
1314
|
+
<div v-for="item in list" :key="item.id" v-memo="[item.id, item.selected]">
|
|
1315
|
+
<ExpensiveComponent :item="item" />
|
|
1316
|
+
</div>
|
|
1317
|
+
</template>
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
### Vite
|
|
1321
|
+
|
|
1322
|
+
```javascript
|
|
1323
|
+
// vite.config.js
|
|
1324
|
+
import { defineConfig } from 'vite';
|
|
1325
|
+
|
|
1326
|
+
export default defineConfig({
|
|
1327
|
+
build: {
|
|
1328
|
+
// Manual chunk splitting (70% faster builds)
|
|
1329
|
+
rollupOptions: {
|
|
1330
|
+
output: {
|
|
1331
|
+
manualChunks: {
|
|
1332
|
+
'vendor': ['react', 'react-dom'],
|
|
1333
|
+
'ui': ['@mui/material', '@mui/icons-material'],
|
|
1334
|
+
'utils': ['lodash-es', 'date-fns']
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
},
|
|
1338
|
+
|
|
1339
|
+
// Minification
|
|
1340
|
+
minify: 'terser',
|
|
1341
|
+
terserOptions: {
|
|
1342
|
+
compress: {
|
|
1343
|
+
drop_console: true,
|
|
1344
|
+
drop_debugger: true
|
|
1345
|
+
}
|
|
1346
|
+
},
|
|
1347
|
+
|
|
1348
|
+
// CSS code splitting
|
|
1349
|
+
cssCodeSplit: true,
|
|
1350
|
+
|
|
1351
|
+
// Source maps only for development
|
|
1352
|
+
sourcemap: process.env.NODE_ENV === 'development'
|
|
1353
|
+
},
|
|
1354
|
+
|
|
1355
|
+
// Dependency pre-bundling
|
|
1356
|
+
optimizeDeps: {
|
|
1357
|
+
include: ['react', 'react-dom', 'lodash-es']
|
|
1358
|
+
},
|
|
1359
|
+
|
|
1360
|
+
// Development optimizations
|
|
1361
|
+
server: {
|
|
1362
|
+
hmr: {
|
|
1363
|
+
overlay: true
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
## Modern Performance Patterns (2024)
|
|
1370
|
+
|
|
1371
|
+
### Streaming SSR
|
|
1372
|
+
|
|
1373
|
+
```jsx
|
|
1374
|
+
// React 18 streaming with Suspense
|
|
1375
|
+
import { Suspense } from 'react';
|
|
1376
|
+
import { renderToReadableStream } from 'react-dom/server';
|
|
1377
|
+
|
|
1378
|
+
async function handler(req, res) {
|
|
1379
|
+
const stream = await renderToReadableStream(
|
|
1380
|
+
<App />,
|
|
1381
|
+
{
|
|
1382
|
+
onError(error) {
|
|
1383
|
+
console.error(error);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
);
|
|
1387
|
+
|
|
1388
|
+
res.setHeader('Content-Type', 'text/html');
|
|
1389
|
+
stream.pipeTo(new WritableStream({
|
|
1390
|
+
write(chunk) {
|
|
1391
|
+
res.write(chunk);
|
|
1392
|
+
},
|
|
1393
|
+
close() {
|
|
1394
|
+
res.end();
|
|
1395
|
+
}
|
|
1396
|
+
}));
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// Component with streaming
|
|
1400
|
+
function App() {
|
|
1401
|
+
return (
|
|
1402
|
+
<html>
|
|
1403
|
+
<body>
|
|
1404
|
+
<Header />
|
|
1405
|
+
<Suspense fallback={<Spinner />}>
|
|
1406
|
+
<Comments /> {/* Streams in when ready */}
|
|
1407
|
+
</Suspense>
|
|
1408
|
+
<Footer />
|
|
1409
|
+
</body>
|
|
1410
|
+
</html>
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
```
|
|
1414
|
+
|
|
1415
|
+
### Islands Architecture
|
|
1416
|
+
|
|
1417
|
+
**Astro example:**
|
|
1418
|
+
```astro
|
|
1419
|
+
---
|
|
1420
|
+
// Static content by default
|
|
1421
|
+
import Header from '../components/Header.astro';
|
|
1422
|
+
import InteractiveWidget from '../components/InteractiveWidget.jsx';
|
|
1423
|
+
---
|
|
1424
|
+
|
|
1425
|
+
<html>
|
|
1426
|
+
<body>
|
|
1427
|
+
<Header />
|
|
1428
|
+
|
|
1429
|
+
<!-- Static content -->
|
|
1430
|
+
<article>
|
|
1431
|
+
<h1>Blog Post</h1>
|
|
1432
|
+
<p>Static content...</p>
|
|
1433
|
+
</article>
|
|
1434
|
+
|
|
1435
|
+
<!-- Interactive island: only this hydrates -->
|
|
1436
|
+
<InteractiveWidget client:visible />
|
|
1437
|
+
|
|
1438
|
+
<footer>Static footer</footer>
|
|
1439
|
+
</body>
|
|
1440
|
+
</html>
|
|
1441
|
+
```
|
|
1442
|
+
|
|
1443
|
+
**Hydration strategies:**
|
|
1444
|
+
```astro
|
|
1445
|
+
<!-- Load immediately -->
|
|
1446
|
+
<Component client:load />
|
|
1447
|
+
|
|
1448
|
+
<!-- Load when idle -->
|
|
1449
|
+
<Component client:idle />
|
|
1450
|
+
|
|
1451
|
+
<!-- Load when visible -->
|
|
1452
|
+
<Component client:visible />
|
|
1453
|
+
|
|
1454
|
+
<!-- Load on media query -->
|
|
1455
|
+
<Component client:media="(max-width: 768px)" />
|
|
1456
|
+
|
|
1457
|
+
<!-- Only render server-side (no JS) -->
|
|
1458
|
+
<Component />
|
|
1459
|
+
```
|
|
1460
|
+
|
|
1461
|
+
### Progressive Hydration
|
|
1462
|
+
|
|
1463
|
+
```jsx
|
|
1464
|
+
// Progressive hydration with react-lazy-hydration
|
|
1465
|
+
import { LazyHydrate } from 'react-lazy-hydration';
|
|
1466
|
+
|
|
1467
|
+
function App() {
|
|
1468
|
+
return (
|
|
1469
|
+
<>
|
|
1470
|
+
<Header />
|
|
1471
|
+
|
|
1472
|
+
{/* Hydrate immediately */}
|
|
1473
|
+
<HeroSection />
|
|
1474
|
+
|
|
1475
|
+
{/* Hydrate when visible */}
|
|
1476
|
+
<LazyHydrate whenVisible>
|
|
1477
|
+
<CommentsSection />
|
|
1478
|
+
</LazyHydrate>
|
|
1479
|
+
|
|
1480
|
+
{/* Hydrate when idle */}
|
|
1481
|
+
<LazyHydrate whenIdle>
|
|
1482
|
+
<RelatedArticles />
|
|
1483
|
+
</LazyHydrate>
|
|
1484
|
+
|
|
1485
|
+
{/* Hydrate on interaction */}
|
|
1486
|
+
<LazyHydrate on:click>
|
|
1487
|
+
<ShareButtons />
|
|
1488
|
+
</LazyHydrate>
|
|
1489
|
+
</>
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
```
|
|
1493
|
+
|
|
1494
|
+
## Performance Monitoring
|
|
1495
|
+
|
|
1496
|
+
### Lighthouse CI
|
|
1497
|
+
|
|
1498
|
+
```yaml
|
|
1499
|
+
# .github/workflows/lighthouse.yml
|
|
1500
|
+
name: Lighthouse CI
|
|
1501
|
+
on: [push, pull_request]
|
|
1502
|
+
|
|
1503
|
+
jobs:
|
|
1504
|
+
lighthouse:
|
|
1505
|
+
runs-on: ubuntu-latest
|
|
1506
|
+
steps:
|
|
1507
|
+
- uses: actions/checkout@v3
|
|
1508
|
+
- name: Install dependencies
|
|
1509
|
+
run: npm ci
|
|
1510
|
+
- name: Build
|
|
1511
|
+
run: npm run build
|
|
1512
|
+
- name: Run Lighthouse CI
|
|
1513
|
+
uses: treosh/lighthouse-ci-action@v10
|
|
1514
|
+
with:
|
|
1515
|
+
urls: |
|
|
1516
|
+
http://localhost:3000
|
|
1517
|
+
http://localhost:3000/about
|
|
1518
|
+
uploadArtifacts: true
|
|
1519
|
+
temporaryPublicStorage: true
|
|
1520
|
+
```
|
|
1521
|
+
|
|
1522
|
+
**lighthouserc.json:**
|
|
1523
|
+
```json
|
|
1524
|
+
{
|
|
1525
|
+
"ci": {
|
|
1526
|
+
"collect": {
|
|
1527
|
+
"numberOfRuns": 3,
|
|
1528
|
+
"startServerCommand": "npm run serve",
|
|
1529
|
+
"url": ["http://localhost:3000/"]
|
|
1530
|
+
},
|
|
1531
|
+
"assert": {
|
|
1532
|
+
"assertions": {
|
|
1533
|
+
"categories:performance": ["error", {"minScore": 0.9}],
|
|
1534
|
+
"categories:accessibility": ["error", {"minScore": 0.9}],
|
|
1535
|
+
"first-contentful-paint": ["error", {"maxNumericValue": 2000}],
|
|
1536
|
+
"largest-contentful-paint": ["error", {"maxNumericValue": 2500}],
|
|
1537
|
+
"cumulative-layout-shift": ["error", {"maxNumericValue": 0.1}],
|
|
1538
|
+
"total-blocking-time": ["error", {"maxNumericValue": 300}]
|
|
1539
|
+
}
|
|
1540
|
+
},
|
|
1541
|
+
"upload": {
|
|
1542
|
+
"target": "temporary-public-storage"
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
```
|
|
1547
|
+
|
|
1548
|
+
### Performance Budgets
|
|
1549
|
+
|
|
1550
|
+
```json
|
|
1551
|
+
// budget.json
|
|
1552
|
+
[
|
|
1553
|
+
{
|
|
1554
|
+
"resourceSizes": [
|
|
1555
|
+
{
|
|
1556
|
+
"resourceType": "document",
|
|
1557
|
+
"budget": 50
|
|
1558
|
+
},
|
|
1559
|
+
{
|
|
1560
|
+
"resourceType": "script",
|
|
1561
|
+
"budget": 300
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
"resourceType": "stylesheet",
|
|
1565
|
+
"budget": 50
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
"resourceType": "image",
|
|
1569
|
+
"budget": 500
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
"resourceType": "font",
|
|
1573
|
+
"budget": 100
|
|
1574
|
+
},
|
|
1575
|
+
{
|
|
1576
|
+
"resourceType": "total",
|
|
1577
|
+
"budget": 1000
|
|
1578
|
+
}
|
|
1579
|
+
],
|
|
1580
|
+
"resourceCounts": [
|
|
1581
|
+
{
|
|
1582
|
+
"resourceType": "third-party",
|
|
1583
|
+
"budget": 10
|
|
1584
|
+
}
|
|
1585
|
+
]
|
|
1586
|
+
}
|
|
1587
|
+
]
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
**Webpack bundle size budget:**
|
|
1591
|
+
```javascript
|
|
1592
|
+
// webpack.config.js
|
|
1593
|
+
module.exports = {
|
|
1594
|
+
performance: {
|
|
1595
|
+
maxAssetSize: 300000, // 300KB
|
|
1596
|
+
maxEntrypointSize: 500000, // 500KB
|
|
1597
|
+
hints: 'error'
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
```
|
|
1601
|
+
|
|
1602
|
+
### Real User Monitoring (RUM)
|
|
1603
|
+
|
|
1604
|
+
```javascript
|
|
1605
|
+
// Using web-vitals library
|
|
1606
|
+
import { onCLS, onFCP, onINP, onLCP, onTTFB } from 'web-vitals';
|
|
1607
|
+
|
|
1608
|
+
function sendToAnalytics(metric) {
|
|
1609
|
+
const body = JSON.stringify({
|
|
1610
|
+
name: metric.name,
|
|
1611
|
+
value: metric.value,
|
|
1612
|
+
rating: metric.rating,
|
|
1613
|
+
delta: metric.delta,
|
|
1614
|
+
id: metric.id,
|
|
1615
|
+
navigationType: metric.navigationType
|
|
1616
|
+
});
|
|
1617
|
+
|
|
1618
|
+
// Use sendBeacon for reliable delivery
|
|
1619
|
+
if (navigator.sendBeacon) {
|
|
1620
|
+
navigator.sendBeacon('/analytics', body);
|
|
1621
|
+
} else {
|
|
1622
|
+
fetch('/analytics', { body, method: 'POST', keepalive: true });
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// Measure all Core Web Vitals
|
|
1627
|
+
onCLS(sendToAnalytics);
|
|
1628
|
+
onFCP(sendToAnalytics);
|
|
1629
|
+
onINP(sendToAnalytics);
|
|
1630
|
+
onLCP(sendToAnalytics);
|
|
1631
|
+
onTTFB(sendToAnalytics);
|
|
1632
|
+
|
|
1633
|
+
// Attribution for debugging
|
|
1634
|
+
import { onLCP } from 'web-vitals/attribution';
|
|
1635
|
+
|
|
1636
|
+
onLCP((metric) => {
|
|
1637
|
+
console.log('LCP element:', metric.attribution.element);
|
|
1638
|
+
console.log('LCP resource:', metric.attribution.url);
|
|
1639
|
+
console.log('LCP time:', metric.value);
|
|
1640
|
+
sendToAnalytics(metric);
|
|
1641
|
+
});
|
|
1642
|
+
```
|
|
1643
|
+
|
|
1644
|
+
**RUM vs Lab Data:**
|
|
1645
|
+
- Lab data (Lighthouse): Controlled environment, consistent results
|
|
1646
|
+
- RUM: Real user conditions, network variability, device diversity
|
|
1647
|
+
- Use both: Lab for development, RUM for production monitoring
|
|
1648
|
+
|
|
1649
|
+
### Chrome DevTools Performance Panel
|
|
1650
|
+
|
|
1651
|
+
**2024 updates:**
|
|
1652
|
+
- Live Core Web Vitals overlay
|
|
1653
|
+
- INP debugging (replaced FID)
|
|
1654
|
+
- Enhanced flame chart
|
|
1655
|
+
- Interaction tracking
|
|
1656
|
+
|
|
1657
|
+
**Workflow:**
|
|
1658
|
+
1. Open DevTools (F12) → Performance tab
|
|
1659
|
+
2. Click Record, interact with page, stop recording
|
|
1660
|
+
3. Analyze:
|
|
1661
|
+
- Main thread activity (find long tasks)
|
|
1662
|
+
- Network waterfall (identify bottlenecks)
|
|
1663
|
+
- Core Web Vitals annotations
|
|
1664
|
+
- Frame rate drops
|
|
1665
|
+
4. Use bottom-up view to identify expensive functions
|
|
1666
|
+
|
|
1667
|
+
## Common Performance Issues
|
|
1668
|
+
|
|
1669
|
+
### Issue 1: Unoptimized Images
|
|
1670
|
+
|
|
1671
|
+
**Impact:** 73% of sites have image LCP
|
|
1672
|
+
|
|
1673
|
+
**Symptoms:**
|
|
1674
|
+
- Slow LCP (>2.5s)
|
|
1675
|
+
- High bandwidth usage
|
|
1676
|
+
- Large LCP element in Lighthouse
|
|
1677
|
+
|
|
1678
|
+
**Solution:**
|
|
1679
|
+
|
|
1680
|
+
1. **Convert to modern formats:**
|
|
1681
|
+
```bash
|
|
1682
|
+
# Using ImageMagick
|
|
1683
|
+
magick input.jpg -quality 85 output.webp
|
|
1684
|
+
magick input.jpg -quality 85 output.avif
|
|
1685
|
+
|
|
1686
|
+
# Using cwebp/cavif
|
|
1687
|
+
cwebp -q 85 input.jpg -o output.webp
|
|
1688
|
+
avifenc -s 5 input.jpg output.avif
|
|
1689
|
+
```
|
|
1690
|
+
|
|
1691
|
+
2. **Implement responsive images:**
|
|
1692
|
+
```html
|
|
1693
|
+
<picture>
|
|
1694
|
+
<source
|
|
1695
|
+
srcset="hero-400.avif 400w, hero-800.avif 800w, hero-1200.avif 1200w"
|
|
1696
|
+
type="image/avif"
|
|
1697
|
+
>
|
|
1698
|
+
<source
|
|
1699
|
+
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
|
|
1700
|
+
type="image/webp"
|
|
1701
|
+
>
|
|
1702
|
+
<img
|
|
1703
|
+
srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
|
|
1704
|
+
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
|
|
1705
|
+
src="hero-800.jpg"
|
|
1706
|
+
alt="Hero image"
|
|
1707
|
+
fetchpriority="high"
|
|
1708
|
+
>
|
|
1709
|
+
</picture>
|
|
1710
|
+
```
|
|
1711
|
+
|
|
1712
|
+
3. **Use image CDN:**
|
|
1713
|
+
```javascript
|
|
1714
|
+
// Cloudinary automatic optimization
|
|
1715
|
+
const imageUrl = cloudinary.url('hero.jpg', {
|
|
1716
|
+
fetch_format: 'auto',
|
|
1717
|
+
quality: 'auto',
|
|
1718
|
+
width: 'auto',
|
|
1719
|
+
dpr: 'auto',
|
|
1720
|
+
crop: 'scale'
|
|
1721
|
+
});
|
|
1722
|
+
```
|
|
1723
|
+
|
|
1724
|
+
### Issue 2: Render-Blocking Resources
|
|
1725
|
+
|
|
1726
|
+
**Impact:** Delays FCP/LCP by seconds
|
|
1727
|
+
|
|
1728
|
+
**Symptoms:**
|
|
1729
|
+
- "Eliminate render-blocking resources" in Lighthouse
|
|
1730
|
+
- Long white screen time
|
|
1731
|
+
- Delayed FCP
|
|
1732
|
+
|
|
1733
|
+
**Solution:**
|
|
1734
|
+
|
|
1735
|
+
1. **Inline critical CSS:**
|
|
1736
|
+
```html
|
|
1737
|
+
<head>
|
|
1738
|
+
<style>
|
|
1739
|
+
/* Critical above-the-fold CSS */
|
|
1740
|
+
body { margin: 0; font-family: system-ui; }
|
|
1741
|
+
.hero { height: 100vh; background: #f0f0f0; }
|
|
1742
|
+
</style>
|
|
1743
|
+
|
|
1744
|
+
<!-- Defer non-critical CSS -->
|
|
1745
|
+
<link rel="preload" as="style" href="main.css" onload="this.onload=null;this.rel='stylesheet'">
|
|
1746
|
+
<noscript><link rel="stylesheet" href="main.css"></noscript>
|
|
1747
|
+
</head>
|
|
1748
|
+
```
|
|
1749
|
+
|
|
1750
|
+
2. **Defer JavaScript:**
|
|
1751
|
+
```html
|
|
1752
|
+
<!-- Defer non-critical scripts -->
|
|
1753
|
+
<script src="main.js" defer></script>
|
|
1754
|
+
|
|
1755
|
+
<!-- Async for independent scripts -->
|
|
1756
|
+
<script src="analytics.js" async></script>
|
|
1757
|
+
|
|
1758
|
+
<!-- Module scripts are deferred by default -->
|
|
1759
|
+
<script type="module" src="app.js"></script>
|
|
1760
|
+
```
|
|
1761
|
+
|
|
1762
|
+
3. **Remove unused CSS:**
|
|
1763
|
+
```bash
|
|
1764
|
+
# Using PurgeCSS
|
|
1765
|
+
npm install --save-dev @fullhuman/postcss-purgecss
|
|
1766
|
+
```
|
|
1767
|
+
|
|
1768
|
+
### Issue 3: Third-Party Script Bloat
|
|
1769
|
+
|
|
1770
|
+
**Impact:** 90%+ sites affected, major contributor to TBT
|
|
1771
|
+
|
|
1772
|
+
**Symptoms:**
|
|
1773
|
+
- High TBT (>200ms)
|
|
1774
|
+
- "Reduce JavaScript execution time" in Lighthouse
|
|
1775
|
+
- Slow INP
|
|
1776
|
+
|
|
1777
|
+
**Solution:**
|
|
1778
|
+
|
|
1779
|
+
1. **Audit third-party scripts:**
|
|
1780
|
+
```javascript
|
|
1781
|
+
// Find third-party impact in DevTools
|
|
1782
|
+
// Coverage tab → Show third-party badges
|
|
1783
|
+
|
|
1784
|
+
// Performance Insights → Third-party usage
|
|
1785
|
+
```
|
|
1786
|
+
|
|
1787
|
+
2. **Lazy load non-critical scripts:**
|
|
1788
|
+
```javascript
|
|
1789
|
+
// Load on interaction
|
|
1790
|
+
let analyticsLoaded = false;
|
|
1791
|
+
|
|
1792
|
+
function loadAnalytics() {
|
|
1793
|
+
if (analyticsLoaded) return;
|
|
1794
|
+
analyticsLoaded = true;
|
|
1795
|
+
|
|
1796
|
+
const script = document.createElement('script');
|
|
1797
|
+
script.src = 'https://analytics.example.com/script.js';
|
|
1798
|
+
document.head.appendChild(script);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
['click', 'scroll', 'keydown'].forEach(event => {
|
|
1802
|
+
window.addEventListener(event, loadAnalytics, { once: true });
|
|
1803
|
+
});
|
|
1804
|
+
|
|
1805
|
+
setTimeout(loadAnalytics, 5000); // Fallback
|
|
1806
|
+
```
|
|
1807
|
+
|
|
1808
|
+
3. **Use facades for heavy embeds:**
|
|
1809
|
+
```javascript
|
|
1810
|
+
// YouTube facade (save ~500KB)
|
|
1811
|
+
class YoutubeFacade extends HTMLElement {
|
|
1812
|
+
connectedCallback() {
|
|
1813
|
+
const videoId = this.getAttribute('videoid');
|
|
1814
|
+
this.innerHTML = `
|
|
1815
|
+
<div class="facade" style="cursor: pointer;">
|
|
1816
|
+
<img src="https://i.ytimg.com/vi/${videoId}/hqdefault.jpg">
|
|
1817
|
+
<button class="play-button">▶</button>
|
|
1818
|
+
</div>
|
|
1819
|
+
`;
|
|
1820
|
+
|
|
1821
|
+
this.querySelector('.facade').addEventListener('click', () => {
|
|
1822
|
+
this.innerHTML = `
|
|
1823
|
+
<iframe
|
|
1824
|
+
src="https://www.youtube.com/embed/${videoId}?autoplay=1"
|
|
1825
|
+
allow="autoplay"
|
|
1826
|
+
allowfullscreen
|
|
1827
|
+
></iframe>
|
|
1828
|
+
`;
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
customElements.define('youtube-facade', YoutubeFacade);
|
|
1834
|
+
```
|
|
1835
|
+
|
|
1836
|
+
### Issue 4: Layout Shifts
|
|
1837
|
+
|
|
1838
|
+
**Impact:** Poor CLS score (>0.1)
|
|
1839
|
+
|
|
1840
|
+
**Symptoms:**
|
|
1841
|
+
- Content jumping during load
|
|
1842
|
+
- "Avoid large layout shifts" in Lighthouse
|
|
1843
|
+
- Elements moving unexpectedly
|
|
1844
|
+
|
|
1845
|
+
**Solution:**
|
|
1846
|
+
|
|
1847
|
+
1. **Reserve space for images:**
|
|
1848
|
+
```html
|
|
1849
|
+
<!-- Always include dimensions -->
|
|
1850
|
+
<img src="photo.jpg" width="800" height="600" alt="Photo">
|
|
1851
|
+
|
|
1852
|
+
<!-- Or use aspect-ratio -->
|
|
1853
|
+
<style>
|
|
1854
|
+
.image-container {
|
|
1855
|
+
aspect-ratio: 16 / 9;
|
|
1856
|
+
}
|
|
1857
|
+
.image-container img {
|
|
1858
|
+
width: 100%;
|
|
1859
|
+
height: 100%;
|
|
1860
|
+
object-fit: cover;
|
|
1861
|
+
}
|
|
1862
|
+
</style>
|
|
1863
|
+
```
|
|
1864
|
+
|
|
1865
|
+
2. **Prevent font layout shifts:**
|
|
1866
|
+
```css
|
|
1867
|
+
/* Use font-display: optional for best CLS */
|
|
1868
|
+
@font-face {
|
|
1869
|
+
font-family: 'Main Font';
|
|
1870
|
+
src: url('/fonts/main.woff2') format('woff2');
|
|
1871
|
+
font-display: optional; /* Prevents FOIT and minimizes shift */
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
/* Or use fallback font metrics matching */
|
|
1875
|
+
@font-face {
|
|
1876
|
+
font-family: 'Main Font Fallback';
|
|
1877
|
+
src: local('Arial');
|
|
1878
|
+
ascent-override: 95%;
|
|
1879
|
+
descent-override: 25%;
|
|
1880
|
+
line-gap-override: 0%;
|
|
1881
|
+
size-adjust: 100%;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
body {
|
|
1885
|
+
font-family: 'Main Font', 'Main Font Fallback', sans-serif;
|
|
1886
|
+
}
|
|
1887
|
+
```
|
|
1888
|
+
|
|
1889
|
+
3. **Use skeleton screens:**
|
|
1890
|
+
```css
|
|
1891
|
+
.skeleton {
|
|
1892
|
+
background: linear-gradient(
|
|
1893
|
+
90deg,
|
|
1894
|
+
#f0f0f0 25%,
|
|
1895
|
+
#e0e0e0 50%,
|
|
1896
|
+
#f0f0f0 75%
|
|
1897
|
+
);
|
|
1898
|
+
background-size: 200% 100%;
|
|
1899
|
+
animation: loading 1.5s infinite;
|
|
1900
|
+
min-height: 200px;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
@keyframes loading {
|
|
1904
|
+
0% { background-position: 200% 0; }
|
|
1905
|
+
100% { background-position: -200% 0; }
|
|
1906
|
+
}
|
|
1907
|
+
```
|
|
1908
|
+
|
|
1909
|
+
### Issue 5: Large JavaScript Bundles
|
|
1910
|
+
|
|
1911
|
+
**Impact:** Slow TTI, high TBT (>200ms)
|
|
1912
|
+
|
|
1913
|
+
**Symptoms:**
|
|
1914
|
+
- "Reduce JavaScript execution time" in Lighthouse
|
|
1915
|
+
- Large bundle sizes (>300KB)
|
|
1916
|
+
- Slow initial load
|
|
1917
|
+
|
|
1918
|
+
**Solution:**
|
|
1919
|
+
|
|
1920
|
+
1. **Analyze bundle:**
|
|
1921
|
+
```bash
|
|
1922
|
+
# webpack-bundle-analyzer
|
|
1923
|
+
npm install --save-dev webpack-bundle-analyzer
|
|
1924
|
+
npm run build -- --analyze
|
|
1925
|
+
```
|
|
1926
|
+
|
|
1927
|
+
2. **Code splitting:**
|
|
1928
|
+
```javascript
|
|
1929
|
+
// Route-based splitting
|
|
1930
|
+
const Home = lazy(() => import('./pages/Home'));
|
|
1931
|
+
const Dashboard = lazy(() => import('./pages/Dashboard'));
|
|
1932
|
+
|
|
1933
|
+
// Component-based splitting
|
|
1934
|
+
const Chart = lazy(() => import('./components/Chart'));
|
|
1935
|
+
|
|
1936
|
+
// Vendor splitting
|
|
1937
|
+
// webpack.config.js
|
|
1938
|
+
optimization: {
|
|
1939
|
+
splitChunks: {
|
|
1940
|
+
chunks: 'all',
|
|
1941
|
+
cacheGroups: {
|
|
1942
|
+
vendor: {
|
|
1943
|
+
test: /[\\/]node_modules[\\/]/,
|
|
1944
|
+
name: 'vendors',
|
|
1945
|
+
priority: 10
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
```
|
|
1951
|
+
|
|
1952
|
+
3. **Tree shaking:**
|
|
1953
|
+
```javascript
|
|
1954
|
+
// Import only what you need
|
|
1955
|
+
import { debounce } from 'lodash-es'; // Good
|
|
1956
|
+
import debounce from 'lodash/debounce'; // Good
|
|
1957
|
+
import _ from 'lodash'; // Bad: imports entire library
|
|
1958
|
+
```
|
|
1959
|
+
|
|
1960
|
+
### Issue 6: Poor Font Loading
|
|
1961
|
+
|
|
1962
|
+
**Impact:** FOIT/FOUT, layout shifts, delayed FCP
|
|
1963
|
+
|
|
1964
|
+
**Symptoms:**
|
|
1965
|
+
- Invisible text during load (FOIT)
|
|
1966
|
+
- Flash of unstyled text (FOUT)
|
|
1967
|
+
- Layout shifts when font loads
|
|
1968
|
+
|
|
1969
|
+
**Solution:**
|
|
1970
|
+
|
|
1971
|
+
1. **Preload critical fonts:**
|
|
1972
|
+
```html
|
|
1973
|
+
<link rel="preload" as="font" type="font/woff2" href="/fonts/main.woff2" crossorigin>
|
|
1974
|
+
```
|
|
1975
|
+
|
|
1976
|
+
2. **Optimize font-display:**
|
|
1977
|
+
```css
|
|
1978
|
+
@font-face {
|
|
1979
|
+
font-family: 'Main Font';
|
|
1980
|
+
src: url('/fonts/main.woff2') format('woff2');
|
|
1981
|
+
font-display: optional; /* Best for CLS */
|
|
1982
|
+
/* font-display: swap; Prevents FOIT but may cause shift */
|
|
1983
|
+
}
|
|
1984
|
+
```
|
|
1985
|
+
|
|
1986
|
+
3. **Subset fonts:**
|
|
1987
|
+
```bash
|
|
1988
|
+
# Using pyftsubset (fonttools)
|
|
1989
|
+
pyftsubset font.ttf \
|
|
1990
|
+
--output-file=font-subset.woff2 \
|
|
1991
|
+
--flavor=woff2 \
|
|
1992
|
+
--unicodes="U+0020-007E" # ASCII characters
|
|
1993
|
+
```
|
|
1994
|
+
|
|
1995
|
+
4. **Use system fonts:**
|
|
1996
|
+
```css
|
|
1997
|
+
body {
|
|
1998
|
+
font-family:
|
|
1999
|
+
-apple-system,
|
|
2000
|
+
BlinkMacSystemFont,
|
|
2001
|
+
'Segoe UI',
|
|
2002
|
+
Roboto,
|
|
2003
|
+
'Helvetica Neue',
|
|
2004
|
+
Arial,
|
|
2005
|
+
sans-serif;
|
|
2006
|
+
}
|
|
2007
|
+
```
|
|
2008
|
+
|
|
2009
|
+
## Performance Audit Workflow
|
|
2010
|
+
|
|
2011
|
+
### 1. Baseline Measurement
|
|
2012
|
+
|
|
2013
|
+
**Run Lighthouse:**
|
|
2014
|
+
```bash
|
|
2015
|
+
lighthouse https://example.com --view --output html json --output-path ./report
|
|
2016
|
+
```
|
|
2017
|
+
|
|
2018
|
+
**Run WebPageTest:**
|
|
2019
|
+
- Visit webpagetest.org
|
|
2020
|
+
- Enter URL, select location and device
|
|
2021
|
+
- Run test 3 times for median results
|
|
2022
|
+
|
|
2023
|
+
**Collect field data:**
|
|
2024
|
+
```javascript
|
|
2025
|
+
// Chrome User Experience Report (CrUX) via PageSpeed Insights API
|
|
2026
|
+
const url = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed';
|
|
2027
|
+
const params = `?url=https://example.com&key=YOUR_API_KEY`;
|
|
2028
|
+
|
|
2029
|
+
fetch(url + params)
|
|
2030
|
+
.then(res => res.json())
|
|
2031
|
+
.then(data => {
|
|
2032
|
+
console.log('Field data:', data.loadingExperience.metrics);
|
|
2033
|
+
});
|
|
2034
|
+
```
|
|
2035
|
+
|
|
2036
|
+
### 2. Identify Bottlenecks
|
|
2037
|
+
|
|
2038
|
+
**Priority order:**
|
|
2039
|
+
1. LCP issues (25% weight) - Images, fonts, server response
|
|
2040
|
+
2. TBT issues (30% weight) - JavaScript execution, long tasks
|
|
2041
|
+
3. CLS issues (25% weight) - Images without dimensions, fonts, dynamic content
|
|
2042
|
+
4. FCP issues (10% weight) - Render-blocking resources
|
|
2043
|
+
5. Other metrics (10% weight)
|
|
2044
|
+
|
|
2045
|
+
### 3. Implement Optimizations
|
|
2046
|
+
|
|
2047
|
+
**High-impact changes first:**
|
|
2048
|
+
1. Optimize LCP image (modern format, CDN, preload)
|
|
2049
|
+
2. Reduce JavaScript (code splitting, remove unused code)
|
|
2050
|
+
3. Fix layout shifts (reserve space, font-display)
|
|
2051
|
+
4. Eliminate render-blocking resources (defer, async, inline critical)
|
|
2052
|
+
5. Optimize third-party scripts (lazy load, facades)
|
|
2053
|
+
|
|
2054
|
+
### 4. Measure Impact
|
|
2055
|
+
|
|
2056
|
+
**Before/after comparison:**
|
|
2057
|
+
```javascript
|
|
2058
|
+
// Track metrics over time
|
|
2059
|
+
const metrics = {
|
|
2060
|
+
before: {
|
|
2061
|
+
LCP: 3200,
|
|
2062
|
+
INP: 450,
|
|
2063
|
+
CLS: 0.18,
|
|
2064
|
+
TBT: 580,
|
|
2065
|
+
score: 62
|
|
2066
|
+
},
|
|
2067
|
+
after: {
|
|
2068
|
+
LCP: 2100, // 34% improvement
|
|
2069
|
+
INP: 180, // 60% improvement
|
|
2070
|
+
CLS: 0.05, // 72% improvement
|
|
2071
|
+
TBT: 150, // 74% improvement
|
|
2072
|
+
score: 94 // 52% improvement
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
```
|
|
2076
|
+
|
|
2077
|
+
### 5. Set Up Monitoring
|
|
2078
|
+
|
|
2079
|
+
**Lighthouse CI:**
|
|
2080
|
+
```yaml
|
|
2081
|
+
# .github/workflows/lighthouse.yml
|
|
2082
|
+
- name: Run Lighthouse CI
|
|
2083
|
+
uses: treosh/lighthouse-ci-action@v10
|
|
2084
|
+
```
|
|
2085
|
+
|
|
2086
|
+
**Real User Monitoring:**
|
|
2087
|
+
```javascript
|
|
2088
|
+
import { onCLS, onFCP, onINP, onLCP } from 'web-vitals';
|
|
2089
|
+
|
|
2090
|
+
[onCLS, onFCP, onINP, onLCP].forEach(fn => fn(sendToAnalytics));
|
|
2091
|
+
```
|
|
2092
|
+
|
|
2093
|
+
## Performance Budget Template
|
|
2094
|
+
|
|
2095
|
+
```json
|
|
2096
|
+
{
|
|
2097
|
+
"performance": {
|
|
2098
|
+
"score": 90,
|
|
2099
|
+
"metrics": {
|
|
2100
|
+
"first-contentful-paint": 1800,
|
|
2101
|
+
"largest-contentful-paint": 2500,
|
|
2102
|
+
"interaction-to-next-paint": 200,
|
|
2103
|
+
"cumulative-layout-shift": 0.1,
|
|
2104
|
+
"total-blocking-time": 200,
|
|
2105
|
+
"speed-index": 3000
|
|
2106
|
+
}
|
|
2107
|
+
},
|
|
2108
|
+
"resourceSizes": {
|
|
2109
|
+
"document": 50,
|
|
2110
|
+
"script": 300,
|
|
2111
|
+
"stylesheet": 50,
|
|
2112
|
+
"image": 500,
|
|
2113
|
+
"font": 100,
|
|
2114
|
+
"total": 1000
|
|
2115
|
+
},
|
|
2116
|
+
"resourceCounts": {
|
|
2117
|
+
"third-party": 10,
|
|
2118
|
+
"total": 50
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
```
|
|
2122
|
+
|
|
2123
|
+
## Business Impact
|
|
2124
|
+
|
|
2125
|
+
### Conversion Impact
|
|
2126
|
+
|
|
2127
|
+
- **1 second delay = 7% conversion loss** (Amazon)
|
|
2128
|
+
- **0.1s improvement = 8% increase in conversions** (Walmart)
|
|
2129
|
+
- **3s load time = 32% abandon rate** (Google)
|
|
2130
|
+
- **53% mobile users abandon after 3s** (Google)
|
|
2131
|
+
- **Page speed is a ranking factor** (Google)
|
|
2132
|
+
|
|
2133
|
+
**Case Studies:**
|
|
2134
|
+
|
|
2135
|
+
- **Pinterest:** Reduced perceived wait times by 40%, increased SEO traffic by 15%, increased sign-ups by 15%
|
|
2136
|
+
- **Zalando:** 0.1s improvement = 0.7% increase in revenue
|
|
2137
|
+
- **AutoAnything:** 50% faster page load = 12-13% increase in sales
|
|
2138
|
+
- **BBC:** Lost 10% of users for every additional second of load time
|
|
2139
|
+
|
|
2140
|
+
### SEO Impact
|
|
2141
|
+
|
|
2142
|
+
- **Core Web Vitals as ranking factor** (June 2021+)
|
|
2143
|
+
- **Page experience signals** affect rankings
|
|
2144
|
+
- **Mobile-first indexing** prioritizes mobile performance
|
|
2145
|
+
- **75% of users pass Core Web Vitals = ranking boost**
|
|
2146
|
+
|
|
2147
|
+
## Quick Reference
|
|
2148
|
+
|
|
2149
|
+
### Commands
|
|
2150
|
+
|
|
2151
|
+
```bash
|
|
2152
|
+
# Lighthouse CLI
|
|
2153
|
+
lighthouse https://example.com --view
|
|
2154
|
+
|
|
2155
|
+
# Lighthouse CI
|
|
2156
|
+
lhci autorun
|
|
2157
|
+
|
|
2158
|
+
# WebPageTest CLI
|
|
2159
|
+
webpagetest test https://example.com --key YOUR_KEY
|
|
2160
|
+
|
|
2161
|
+
# Chrome DevTools headless
|
|
2162
|
+
chrome --headless --disable-gpu --screenshot https://example.com
|
|
2163
|
+
|
|
2164
|
+
# Image optimization
|
|
2165
|
+
cwebp -q 85 input.jpg -o output.webp
|
|
2166
|
+
avifenc -s 5 input.jpg output.avif
|
|
2167
|
+
|
|
2168
|
+
# Bundle analysis
|
|
2169
|
+
webpack-bundle-analyzer dist/stats.json
|
|
2170
|
+
```
|
|
2171
|
+
|
|
2172
|
+
### Optimization Checklist
|
|
2173
|
+
|
|
2174
|
+
**Images:**
|
|
2175
|
+
- [ ] Convert to WebP/AVIF
|
|
2176
|
+
- [ ] Implement responsive images with srcset
|
|
2177
|
+
- [ ] Add width/height attributes
|
|
2178
|
+
- [ ] Lazy load below-fold images
|
|
2179
|
+
- [ ] Preload LCP image with fetchpriority="high"
|
|
2180
|
+
- [ ] Use CDN with automatic optimization
|
|
2181
|
+
|
|
2182
|
+
**JavaScript:**
|
|
2183
|
+
- [ ] Code splitting (route-based and component-based)
|
|
2184
|
+
- [ ] Tree shaking enabled
|
|
2185
|
+
- [ ] Remove unused code
|
|
2186
|
+
- [ ] Defer non-critical scripts
|
|
2187
|
+
- [ ] Lazy load third-party scripts
|
|
2188
|
+
- [ ] Use web workers for heavy computations
|
|
2189
|
+
|
|
2190
|
+
**CSS:**
|
|
2191
|
+
- [ ] Extract and inline critical CSS
|
|
2192
|
+
- [ ] Remove unused CSS (PurgeCSS)
|
|
2193
|
+
- [ ] Defer non-critical CSS
|
|
2194
|
+
- [ ] Minify CSS
|
|
2195
|
+
- [ ] Use modern CSS features (containment, content-visibility)
|
|
2196
|
+
|
|
2197
|
+
**Fonts:**
|
|
2198
|
+
- [ ] Preload critical fonts
|
|
2199
|
+
- [ ] Use font-display: optional or swap
|
|
2200
|
+
- [ ] Subset fonts
|
|
2201
|
+
- [ ] Consider system fonts
|
|
2202
|
+
|
|
2203
|
+
**Caching:**
|
|
2204
|
+
- [ ] Set Cache-Control headers
|
|
2205
|
+
- [ ] Implement service worker
|
|
2206
|
+
- [ ] Use CDN edge caching
|
|
2207
|
+
- [ ] Enable stale-while-revalidate
|
|
2208
|
+
|
|
2209
|
+
**Server:**
|
|
2210
|
+
- [ ] Enable HTTP/2 or HTTP/3
|
|
2211
|
+
- [ ] Implement 103 Early Hints
|
|
2212
|
+
- [ ] Optimize TTFB (<800ms)
|
|
2213
|
+
- [ ] Enable compression (Brotli/gzip)
|
|
2214
|
+
|
|
2215
|
+
**Monitoring:**
|
|
2216
|
+
- [ ] Set up Lighthouse CI
|
|
2217
|
+
- [ ] Implement Real User Monitoring
|
|
2218
|
+
- [ ] Define performance budgets
|
|
2219
|
+
- [ ] Set up alerting
|
|
2220
|
+
|
|
2221
|
+
### Tools List
|
|
2222
|
+
|
|
2223
|
+
**Measurement:**
|
|
2224
|
+
- Lighthouse (CLI, DevTools, CI)
|
|
2225
|
+
- WebPageTest
|
|
2226
|
+
- Chrome DevTools Performance Panel
|
|
2227
|
+
- PageSpeed Insights (includes CrUX data)
|
|
2228
|
+
|
|
2229
|
+
**Monitoring:**
|
|
2230
|
+
- web-vitals library
|
|
2231
|
+
- Lighthouse CI
|
|
2232
|
+
- SpeedCurve
|
|
2233
|
+
- Calibre
|
|
2234
|
+
- Vercel Analytics
|
|
2235
|
+
- Cloudflare Web Analytics
|
|
2236
|
+
|
|
2237
|
+
**Optimization:**
|
|
2238
|
+
- ImageMagick, cwebp, avifenc (images)
|
|
2239
|
+
- webpack-bundle-analyzer (bundle analysis)
|
|
2240
|
+
- PurgeCSS (unused CSS)
|
|
2241
|
+
- Critical (critical CSS extraction)
|
|
2242
|
+
- Workbox (service workers)
|
|
2243
|
+
- Partytown (third-party scripts in web workers)
|
|
2244
|
+
|
|
2245
|
+
**Testing:**
|
|
2246
|
+
- Lighthouse
|
|
2247
|
+
- Chrome DevTools
|
|
2248
|
+
- WebPageTest
|
|
2249
|
+
- GTmetrix
|
|
2250
|
+
- Pingdom
|
|
2251
|
+
|
|
2252
|
+
## Anti-Patterns
|
|
2253
|
+
|
|
2254
|
+
### What NOT to Do
|
|
2255
|
+
|
|
2256
|
+
❌ **Loading all resources eagerly**
|
|
2257
|
+
- Don't load all JavaScript upfront
|
|
2258
|
+
- Don't load all images without lazy loading
|
|
2259
|
+
- Don't preload everything
|
|
2260
|
+
|
|
2261
|
+
❌ **Ignoring image optimization**
|
|
2262
|
+
- Don't use unoptimized JPEGs/PNGs
|
|
2263
|
+
- Don't skip responsive images
|
|
2264
|
+
- Don't forget width/height attributes
|
|
2265
|
+
|
|
2266
|
+
❌ **No caching strategy**
|
|
2267
|
+
- Don't skip Cache-Control headers
|
|
2268
|
+
- Don't forget service worker caching
|
|
2269
|
+
- Don't ignore CDN caching
|
|
2270
|
+
|
|
2271
|
+
❌ **Blocking render with non-critical resources**
|
|
2272
|
+
- Don't include all CSS in head without deferring
|
|
2273
|
+
- Don't use synchronous scripts in head
|
|
2274
|
+
- Don't skip async/defer on third-party scripts
|
|
2275
|
+
|
|
2276
|
+
❌ **Ignoring third-party script impact**
|
|
2277
|
+
- Don't load analytics/ads synchronously
|
|
2278
|
+
- Don't embed heavy widgets without facades
|
|
2279
|
+
- Don't skip third-party script auditing
|
|
2280
|
+
|
|
2281
|
+
❌ **No performance monitoring**
|
|
2282
|
+
- Don't skip Lighthouse CI
|
|
2283
|
+
- Don't ignore Real User Monitoring
|
|
2284
|
+
- Don't forget performance budgets
|
|
2285
|
+
|
|
2286
|
+
❌ **Optimizing without measurement**
|
|
2287
|
+
- Don't optimize blindly
|
|
2288
|
+
- Don't skip baseline measurements
|
|
2289
|
+
- Don't forget A/B testing
|
|
2290
|
+
|
|
2291
|
+
❌ **Premature optimization**
|
|
2292
|
+
- Don't optimize before profiling
|
|
2293
|
+
- Don't add complexity without data
|
|
2294
|
+
- Don't optimize the wrong things
|
|
2295
|
+
|
|
2296
|
+
## Related Skills
|
|
2297
|
+
|
|
2298
|
+
- **nextjs-local-dev** - Next.js-specific optimizations
|
|
2299
|
+
- **vite-local-dev** - Vite build optimizations
|
|
2300
|
+
- **docker-containerization** - Optimizing Docker images for performance
|
|
2301
|
+
- **systematic-debugging** - Debugging performance issues
|
|
2302
|
+
|
|
2303
|
+
---
|
|
2304
|
+
|
|
2305
|
+
**Remember:** Performance is a feature, not an afterthought. Every millisecond counts toward user experience, conversions, and SEO. Measure, optimize, and monitor continuously.
|