react-media-optimizer 1.0.1 → 1.0.2
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.
- package/README.md +493 -186
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,393 +1,700 @@
|
|
|
1
1
|
# 🚀 React Media Optimizer
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
[](https://bundlephobia.com/package/react-media-optimizer)
|
|
6
|
-
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/react-media-optimizer)[](https://npmjs.com/package/react-media-optimizer)[](https://bundlephobia.com/package/react-media-optimizer)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)[](https://www.typescriptlang.org/)
|
|
7
|
+
|
|
8
|
+
|
|
8
9
|
|
|
9
10
|
**Drop-in image & video optimization for React applications.** Automatically compress, lazy-load, and convert media to improve performance, UX, and SEO with minimal effort.
|
|
10
11
|
|
|
12
|
+
|
|
13
|
+
|
|
11
14
|
> 📊 **Average improvements:** 60% faster LCP, 75% smaller images, 40% better SEO scores
|
|
12
15
|
|
|
16
|
+
|
|
17
|
+
|
|
13
18
|
---
|
|
14
19
|
|
|
20
|
+
|
|
21
|
+
|
|
15
22
|
## ✨ Why Choose React Media Optimizer?
|
|
16
23
|
|
|
24
|
+
|
|
25
|
+
|
|
17
26
|
| Feature | Benefit | Impact |
|
|
27
|
+
|
|
18
28
|
|---------|---------|--------|
|
|
29
|
+
|
|
19
30
|
| **Auto Lazy Loading** | Images/videos load only when visible | ⬇️ 50-80% initial page weight |
|
|
31
|
+
|
|
20
32
|
| **WebP/WebM Conversion** | Modern formats with better compression | ⬇️ 25-35% smaller file sizes |
|
|
33
|
+
|
|
21
34
|
| **Client-side Compression** | Reduce upload sizes before server | ⬇️ 60-80% upload bandwidth |
|
|
35
|
+
|
|
22
36
|
| **SSR/SSG Safe** | Works with Next.js, Gatsby, Remix | ✅ Zero hydration errors |
|
|
37
|
+
|
|
23
38
|
| **Zero Configuration** | Sensible defaults out of the box | ⏱️ 5-minute integration |
|
|
24
39
|
|
|
40
|
+
|
|
41
|
+
|
|
25
42
|
---
|
|
26
43
|
|
|
44
|
+
|
|
45
|
+
|
|
27
46
|
## 📦 Installation
|
|
28
47
|
|
|
48
|
+
|
|
49
|
+
|
|
29
50
|
Install using your preferred package manager.
|
|
30
51
|
|
|
52
|
+
|
|
53
|
+
|
|
31
54
|
### npm
|
|
55
|
+
|
|
32
56
|
```bash
|
|
33
|
-
npm
|
|
57
|
+
npm install react-media-optimizer
|
|
34
58
|
```
|
|
59
|
+
|
|
35
60
|
### yarn
|
|
36
61
|
```bash
|
|
37
|
-
yarn
|
|
62
|
+
yarn add react-media-optimizer
|
|
38
63
|
```
|
|
64
|
+
|
|
39
65
|
### pnpm
|
|
66
|
+
|
|
40
67
|
```bash
|
|
41
|
-
pnpm
|
|
68
|
+
pnpm add react-media-optimizer
|
|
42
69
|
```
|
|
70
|
+
|
|
43
71
|
## 📌 Peer Dependencies
|
|
72
|
+
|
|
44
73
|
### This package requires React 16.8+ (Hooks support).
|
|
45
74
|
|
|
75
|
+
|
|
76
|
+
|
|
46
77
|
|
|
47
78
|
```jsx
|
|
48
79
|
{
|
|
49
|
-
|
|
50
|
-
|
|
80
|
+
"react": ">=16.8.0",
|
|
81
|
+
"react-dom": ">=16.8.0"
|
|
51
82
|
}
|
|
52
83
|
```
|
|
53
84
|
|
|
54
|
-
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
55
88
|
|
|
56
89
|
## 🚀 Quick Start
|
|
57
90
|
|
|
91
|
+
|
|
92
|
+
|
|
58
93
|
### 1. Optimized Image (Component)
|
|
59
94
|
|
|
95
|
+
|
|
96
|
+
|
|
60
97
|
```jsx
|
|
61
|
-
import { OptimizedImage } from
|
|
62
|
-
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
98
|
+
import { OptimizedImage } from 'react-media-optimizer';
|
|
99
|
+
|
|
100
|
+
function HeroSection() {
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
|
|
104
|
+
<OptimizedImage
|
|
105
|
+
|
|
106
|
+
src="https://example.com/hero-banner.jpg"
|
|
107
|
+
|
|
108
|
+
alt="Product showcase"
|
|
109
|
+
|
|
110
|
+
width={1920}
|
|
111
|
+
|
|
112
|
+
height={1080}
|
|
113
|
+
|
|
114
|
+
lazy={true}
|
|
115
|
+
|
|
116
|
+
webp={true}
|
|
117
|
+
|
|
118
|
+
quality={85}
|
|
119
|
+
|
|
120
|
+
placeholderSrc="/blur-placeholder.jpg"
|
|
121
|
+
|
|
122
|
+
className="rounded-lg shadow-xl"
|
|
123
|
+
|
|
124
|
+
/>
|
|
125
|
+
|
|
126
|
+
);
|
|
127
|
+
}
|
|
78
128
|
```
|
|
129
|
+
|
|
79
130
|
### 2. Optimized Image (Hook for Custom Use)
|
|
80
131
|
|
|
132
|
+
|
|
133
|
+
|
|
81
134
|
```jsx
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
135
|
+
|
|
136
|
+
import { useOptimizedImage } from 'react-media-optimizer';
|
|
137
|
+
|
|
138
|
+
function CustomImageGallery({ images }) {
|
|
139
|
+
|
|
140
|
+
return images.map((image) => {
|
|
141
|
+
|
|
142
|
+
const { src, isLoading, error, elementRef } = useOptimizedImage({
|
|
143
|
+
|
|
144
|
+
src: image.url,
|
|
145
|
+
|
|
146
|
+
lazy: true,
|
|
147
|
+
|
|
148
|
+
webp: true,
|
|
149
|
+
|
|
150
|
+
quality: 90,
|
|
151
|
+
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
|
|
156
|
+
<div key={image.id} className="gallery-item">
|
|
157
|
+
|
|
158
|
+
<img
|
|
159
|
+
|
|
160
|
+
ref={elementRef}
|
|
161
|
+
|
|
162
|
+
src={src}
|
|
163
|
+
|
|
164
|
+
alt={image.title}
|
|
165
|
+
|
|
166
|
+
loading="lazy"
|
|
167
|
+
|
|
168
|
+
className={isLoading ? 'opacity-50' : 'opacity-100'}
|
|
169
|
+
|
|
170
|
+
/>
|
|
171
|
+
|
|
172
|
+
{isLoading && <span className="loader">Loading...</span>}
|
|
173
|
+
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
});
|
|
179
|
+
|
|
106
180
|
}
|
|
181
|
+
|
|
107
182
|
```
|
|
108
183
|
|
|
184
|
+
|
|
185
|
+
|
|
109
186
|
### **3. API Reference Section:**
|
|
110
187
|
|
|
111
188
|
```jsx
|
|
112
|
-
import { OptimizedVideo } from
|
|
113
|
-
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
189
|
+
import { OptimizedVideo } from 'react-media-optimizer';
|
|
190
|
+
|
|
191
|
+
function ProductDemo() {
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
|
|
195
|
+
<OptimizedVideo
|
|
196
|
+
|
|
197
|
+
src="https://example.com/demo.mp4"
|
|
198
|
+
|
|
199
|
+
poster="/video-poster.jpg"
|
|
200
|
+
|
|
201
|
+
width="100%"
|
|
202
|
+
|
|
203
|
+
height="auto"
|
|
204
|
+
|
|
205
|
+
lazy={true}
|
|
206
|
+
|
|
207
|
+
webm={true}
|
|
208
|
+
|
|
209
|
+
mp4={true}
|
|
210
|
+
|
|
211
|
+
controls
|
|
212
|
+
|
|
213
|
+
autoPlay={false}
|
|
214
|
+
|
|
215
|
+
muted
|
|
216
|
+
|
|
217
|
+
playsInline
|
|
218
|
+
|
|
219
|
+
/>
|
|
220
|
+
|
|
221
|
+
);
|
|
222
|
+
|
|
130
223
|
}
|
|
224
|
+
|
|
131
225
|
```
|
|
226
|
+
|
|
132
227
|
## 📖 API Reference
|
|
133
228
|
|
|
229
|
+
|
|
230
|
+
|
|
134
231
|
### `<OptimizedImage />` Component
|
|
135
232
|
|
|
233
|
+
|
|
234
|
+
|
|
136
235
|
| Prop | Type | Default | Description |
|
|
236
|
+
|
|
137
237
|
|------|------|---------|-------------|
|
|
238
|
+
|
|
138
239
|
| **src** | `string` | **Required** | Image source URL |
|
|
240
|
+
|
|
139
241
|
| **alt** | `string` | `""` | Accessibility description |
|
|
242
|
+
|
|
140
243
|
| **lazy** | `boolean` | `true` | Enable lazy loading |
|
|
244
|
+
|
|
141
245
|
| **webp** | `boolean` | `true` | Convert to WebP when supported |
|
|
246
|
+
|
|
142
247
|
| **quality** | `number` | `85` | Image quality (1-100) |
|
|
248
|
+
|
|
143
249
|
| **placeholderSrc** | `string` | `undefined` | Loading placeholder image |
|
|
250
|
+
|
|
144
251
|
| **fallbackSrc** | `string` | `undefined` | Fallback on error |
|
|
252
|
+
|
|
145
253
|
| **showLoadingIndicator** | `boolean` | `true` | Visual loading state |
|
|
146
254
|
|
|
255
|
+
|
|
256
|
+
|
|
147
257
|
### `useOptimizedImage()` Hook
|
|
148
258
|
|
|
259
|
+
|
|
260
|
+
|
|
149
261
|
```typescript
|
|
150
|
-
interface
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
onError?: () => void;
|
|
158
|
-
}
|
|
262
|
+
interface UseOptimizedImageOptions {
|
|
263
|
+
|
|
264
|
+
src: string;
|
|
265
|
+
|
|
266
|
+
lazy?: boolean; // default: true
|
|
267
|
+
|
|
268
|
+
webp?: boolean; // default: true
|
|
159
269
|
|
|
270
|
+
quality?: number; // default: 85
|
|
271
|
+
|
|
272
|
+
fallbackSrc?: string;
|
|
273
|
+
|
|
274
|
+
onLoad?: () => void;
|
|
275
|
+
|
|
276
|
+
onError?: () => void;
|
|
277
|
+
|
|
278
|
+
}
|
|
160
279
|
// Returns:
|
|
280
|
+
|
|
161
281
|
const {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
282
|
+
|
|
283
|
+
src, // Optimized source URL
|
|
284
|
+
|
|
285
|
+
isLoading, // Loading state
|
|
286
|
+
|
|
287
|
+
error, // Error object if failed
|
|
288
|
+
|
|
289
|
+
elementRef, // React ref for lazy loading
|
|
290
|
+
|
|
166
291
|
} = useOptimizedImage(options);
|
|
167
|
-
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
|
|
168
295
|
### <OptimizedVideo /> Component
|
|
169
296
|
|
|
297
|
+
|
|
298
|
+
|
|
170
299
|
| Prop | Type | Default | Description |
|
|
300
|
+
|
|
171
301
|
|------|------|---------|-------------|
|
|
302
|
+
|
|
172
303
|
| **src** | `string` | **Required** | Video source URL (`.mp4` or `.webm`) |
|
|
304
|
+
|
|
173
305
|
| **poster** | `string` | `undefined` | Video poster image |
|
|
306
|
+
|
|
174
307
|
| **lazy** | `boolean` | `true` | Lazy load video |
|
|
308
|
+
|
|
175
309
|
| **webm** | `boolean` | `true` | Prefer WebM format |
|
|
310
|
+
|
|
176
311
|
| **mp4** | `boolean` | `true` | Include MP4 fallback |
|
|
177
312
|
|
|
313
|
+
|
|
314
|
+
|
|
178
315
|
## 🛠️ Advanced Features
|
|
316
|
+
|
|
179
317
|
### 📦 Image Compression Before Upload
|
|
180
318
|
|
|
319
|
+
|
|
320
|
+
|
|
181
321
|
```jsx
|
|
182
|
-
import { compressImage, calculateSizeReduction } from
|
|
183
|
-
|
|
184
|
-
async
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
322
|
+
import { compressImage, calculateSizeReduction } from 'react-media-optimizer';
|
|
323
|
+
|
|
324
|
+
async function handleImageUpload(file) {
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
|
|
328
|
+
const compressedFile = await compressImage(file, {
|
|
329
|
+
|
|
330
|
+
quality: 0.8, // 80% quality
|
|
331
|
+
|
|
332
|
+
maxWidth: 1920, // Resize if wider
|
|
333
|
+
|
|
334
|
+
maxHeight: 1080, // Resize if taller
|
|
335
|
+
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
const reduction = calculateSizeReduction(
|
|
341
|
+
|
|
342
|
+
file.size,
|
|
343
|
+
|
|
344
|
+
compressedFile.size
|
|
345
|
+
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
// Example output: "75.3% smaller"
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
return compressedFile;
|
|
353
|
+
|
|
354
|
+
} catch (error) {
|
|
355
|
+
|
|
356
|
+
console.error('Compression failed:', error);
|
|
357
|
+
|
|
358
|
+
return file; // Fallback to original
|
|
359
|
+
|
|
203
360
|
}
|
|
361
|
+
|
|
362
|
+
}
|
|
363
|
+
|
|
204
364
|
```
|
|
205
365
|
|
|
366
|
+
|
|
367
|
+
|
|
206
368
|
|
|
207
369
|
---
|
|
208
370
|
|
|
371
|
+
|
|
372
|
+
|
|
209
373
|
### 🖼 WebP Detection & Conversion
|
|
210
374
|
|
|
211
|
-
|
|
375
|
+
|
|
212
376
|
|
|
213
|
-
|
|
377
|
+
```jsx
|
|
378
|
+
import { supportsWebP, convertToWebP } from 'react-media-optimizer';
|
|
214
379
|
|
|
215
380
|
// Detect browser support
|
|
216
|
-
|
|
381
|
+
|
|
382
|
+
const webpSupported = await supportsWebP();
|
|
383
|
+
|
|
384
|
+
|
|
217
385
|
|
|
218
386
|
// Convert URLs (requires CDN support)
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
387
|
+
|
|
388
|
+
const imageUrl = 'https://example.com/image.jpg';
|
|
389
|
+
|
|
390
|
+
const optimizedUrl = webpSupported
|
|
391
|
+
|
|
392
|
+
? convertToWebP(imageUrl)
|
|
393
|
+
|
|
394
|
+
: imageUrl;
|
|
395
|
+
|
|
223
396
|
```
|
|
397
|
+
|
|
224
398
|
## 📊 Performance Impact
|
|
399
|
+
|
|
225
400
|
Before & After Comparison:
|
|
226
401
|
|
|
402
|
+
|
|
403
|
+
|
|
227
404
|
| Metric | Standard Images | With React Media Optimizer | Improvement |
|
|
405
|
+
|
|
228
406
|
|--------|----------------|---------------------------|------------|
|
|
407
|
+
|
|
229
408
|
| Largest Contentful Paint | 4.2s | 1.1s | ⬇️ 74% faster |
|
|
409
|
+
|
|
230
410
|
| Total Page Weight | 8.7 MB | 1.9 MB | ⬇️ 78% smaller |
|
|
411
|
+
|
|
231
412
|
| Time to Interactive | 5.8s | 2.3s | ⬇️ 60% faster |
|
|
413
|
+
|
|
232
414
|
| SEO Score | 72/100 | 94/100 | ⬆️ 22 points |
|
|
233
415
|
|
|
416
|
+
|
|
417
|
+
|
|
234
418
|
*Based on average e-commerce site with 50 images*
|
|
235
419
|
|
|
420
|
+
|
|
421
|
+
|
|
236
422
|
## 🏗️ Framework Integration
|
|
237
423
|
|
|
424
|
+
|
|
425
|
+
|
|
238
426
|
### Next.js
|
|
239
427
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
```js
|
|
431
|
+
import { OptimizedImage } from 'react-media-optimizer';
|
|
432
|
+
|
|
433
|
+
export default function HomePage() {
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
|
|
437
|
+
<OptimizedImage
|
|
438
|
+
|
|
439
|
+
src="/nextjs-optimized.jpg"
|
|
440
|
+
|
|
441
|
+
alt="Next.js optimized"
|
|
442
|
+
|
|
443
|
+
width={1200}
|
|
444
|
+
|
|
445
|
+
height={630}
|
|
446
|
+
|
|
447
|
+
priority={true} // Load immediately for LCP
|
|
448
|
+
|
|
449
|
+
/>
|
|
450
|
+
|
|
451
|
+
);
|
|
452
|
+
|
|
254
453
|
}
|
|
255
454
|
|
|
455
|
+
|
|
456
|
+
|
|
256
457
|
```
|
|
458
|
+
|
|
257
459
|
### Gatsby
|
|
258
460
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
```js
|
|
464
|
+
import { OptimizedImage } from 'react-media-optimizer';
|
|
465
|
+
|
|
466
|
+
const IndexPage = () => (
|
|
467
|
+
|
|
468
|
+
<OptimizedImage
|
|
469
|
+
|
|
470
|
+
src={data.file.publicURL}
|
|
471
|
+
|
|
472
|
+
alt="Gatsby site"
|
|
473
|
+
|
|
474
|
+
width={800}
|
|
475
|
+
|
|
476
|
+
height={600}
|
|
477
|
+
|
|
478
|
+
lazy={false} // Critical image
|
|
479
|
+
|
|
480
|
+
/>
|
|
481
|
+
|
|
271
482
|
);
|
|
272
483
|
|
|
273
|
-
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
export default IndexPage;
|
|
487
|
+
|
|
274
488
|
```
|
|
489
|
+
|
|
275
490
|
### Remix
|
|
276
491
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
```tsx
|
|
495
|
+
import { OptimizedImage } from 'react-media-optimizer';
|
|
496
|
+
|
|
497
|
+
export default function Index() {
|
|
498
|
+
|
|
499
|
+
return (
|
|
500
|
+
|
|
501
|
+
<OptimizedImage
|
|
502
|
+
|
|
503
|
+
src="/remix-image.jpg"
|
|
504
|
+
|
|
505
|
+
alt="Remix app"
|
|
506
|
+
|
|
507
|
+
width={800}
|
|
508
|
+
|
|
509
|
+
height={400}
|
|
510
|
+
|
|
511
|
+
webp={true}
|
|
512
|
+
|
|
513
|
+
/>
|
|
514
|
+
|
|
515
|
+
);
|
|
516
|
+
|
|
291
517
|
}
|
|
518
|
+
|
|
292
519
|
```
|
|
520
|
+
|
|
293
521
|
---
|
|
522
|
+
|
|
294
523
|
## ⚡ Best Practices
|
|
524
|
+
|
|
295
525
|
### 1. Prioritize Critical Images
|
|
296
526
|
|
|
527
|
+
|
|
528
|
+
|
|
297
529
|
```jsx
|
|
530
|
+
|
|
298
531
|
// Above-the-fold hero image
|
|
532
|
+
|
|
299
533
|
<OptimizedImage
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
534
|
+
|
|
535
|
+
src="/hero.jpg"
|
|
536
|
+
|
|
537
|
+
alt="Hero"
|
|
538
|
+
|
|
539
|
+
lazy={false} // Load immediately
|
|
540
|
+
|
|
541
|
+
priority={true}
|
|
542
|
+
|
|
304
543
|
/>
|
|
305
544
|
|
|
545
|
+
|
|
546
|
+
|
|
306
547
|
// Below-the-fold gallery images
|
|
548
|
+
|
|
307
549
|
<OptimizedImage
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
550
|
+
|
|
551
|
+
src="/gallery-1.jpg"
|
|
552
|
+
|
|
553
|
+
alt="Gallery item"
|
|
554
|
+
|
|
555
|
+
lazy={true} // Lazy load
|
|
556
|
+
|
|
311
557
|
/>
|
|
558
|
+
|
|
312
559
|
```
|
|
313
560
|
|
|
561
|
+
|
|
562
|
+
|
|
314
563
|
### 2. Use Placeholders for Better UX
|
|
315
564
|
|
|
565
|
+
|
|
316
566
|
```jsx
|
|
567
|
+
|
|
317
568
|
<OptimizedImage
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
569
|
+
|
|
570
|
+
src="/product.jpg"
|
|
571
|
+
|
|
572
|
+
alt="Product"
|
|
573
|
+
|
|
574
|
+
placeholderSrc="/blur-placeholder.jpg"
|
|
575
|
+
|
|
576
|
+
showLoadingIndicator={true}
|
|
577
|
+
|
|
322
578
|
/>
|
|
579
|
+
|
|
323
580
|
```
|
|
581
|
+
|
|
324
582
|
## 3. Set Appropriate Quality
|
|
325
583
|
|
|
584
|
+
|
|
585
|
+
|
|
326
586
|
```jsx
|
|
587
|
+
|
|
327
588
|
// High quality for product photos
|
|
328
|
-
<OptimizedImage quality={90} />
|
|
329
589
|
|
|
330
|
-
|
|
331
|
-
|
|
590
|
+
<OptimizedImage quality={90} />
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
// Medium quality for thumbnails
|
|
595
|
+
|
|
596
|
+
<OptimizedImage quality={70} />
|
|
597
|
+
|
|
598
|
+
|
|
332
599
|
|
|
333
600
|
// Low quality for background images
|
|
334
|
-
|
|
601
|
+
|
|
602
|
+
<OptimizedImage quality={50} />
|
|
603
|
+
|
|
335
604
|
```
|
|
336
605
|
|
|
606
|
+
|
|
607
|
+
|
|
337
608
|
## 🐛 Troubleshooting
|
|
609
|
+
|
|
338
610
|
| Issue | Solution |
|
|
611
|
+
|
|
339
612
|
|-------|---------|
|
|
613
|
+
|
|
340
614
|
| Images not lazy loading | Ensure parent container has `overflow: auto` or `overflow: scroll` |
|
|
615
|
+
|
|
341
616
|
| WebP not working | Check if your CDN supports auto-format conversion |
|
|
617
|
+
|
|
342
618
|
| Compression fails | Verify file is an image and Canvas API is supported |
|
|
619
|
+
|
|
343
620
|
| TypeScript errors | Update to latest version or check peer dependencies |
|
|
344
621
|
|
|
622
|
+
|
|
623
|
+
|
|
345
624
|
### Debug Mode:
|
|
346
625
|
|
|
626
|
+
|
|
627
|
+
|
|
347
628
|
```jsx
|
|
629
|
+
|
|
348
630
|
<OptimizedImage
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
631
|
+
|
|
632
|
+
src="/image.jpg"
|
|
633
|
+
|
|
634
|
+
alt="Debug"
|
|
635
|
+
|
|
636
|
+
debug={true} // Logs optimization steps
|
|
637
|
+
|
|
352
638
|
/>
|
|
639
|
+
|
|
353
640
|
```
|
|
641
|
+
|
|
354
642
|
## 🔄 Migration Guide
|
|
643
|
+
|
|
355
644
|
### From standard <img> tags
|
|
645
|
+
|
|
356
646
|
```diff
|
|
647
|
+
|
|
357
648
|
- <img src="/image.jpg" alt="Example" />
|
|
358
|
-
|
|
359
|
-
+
|
|
360
|
-
|
|
361
|
-
+
|
|
362
|
-
|
|
649
|
+
|
|
650
|
+
+ <OptimizedImage
|
|
651
|
+
|
|
652
|
+
+ src="/image.jpg"
|
|
653
|
+
|
|
654
|
+
+ alt="Example"
|
|
655
|
+
|
|
656
|
+
+ width={800}
|
|
657
|
+
|
|
658
|
+
+ height={600}
|
|
659
|
+
|
|
363
660
|
+ />
|
|
661
|
+
|
|
364
662
|
```
|
|
663
|
+
|
|
365
664
|
### From Next.js Image
|
|
665
|
+
|
|
366
666
|
```diff
|
|
667
|
+
|
|
367
668
|
- import Image from 'next/image';
|
|
669
|
+
|
|
368
670
|
- <Image src="/img.jpg" alt="Example" width={800} height={600} />
|
|
671
|
+
|
|
369
672
|
+ import { OptimizedImage } from 'react-media-optimizer';
|
|
370
|
-
+ <OptimizedImage src="/img.jpg" alt="Example" width={800} height={600} />
|
|
371
|
-
```
|
|
372
673
|
|
|
373
|
-
|
|
674
|
+
+ <OptimizedImage src="/img.jpg" alt="Example" width={800} height={600} />
|
|
374
675
|
|
|
375
|
-
|
|
376
|
-
- ✅ Client-side compression
|
|
377
|
-
- ✅ WebP/WebM detection
|
|
378
|
-
- ✅ SSR/SSG compatibility
|
|
379
|
-
- ✅ TypeScript support
|
|
676
|
+
```
|
|
380
677
|
|
|
381
|
-
|
|
678
|
+
|
|
382
679
|
|
|
383
|
-
|
|
680
|
+
## ✨ Features
|
|
384
681
|
|
|
682
|
+
|
|
385
683
|
|
|
684
|
+
- ✅ Image & video lazy loading
|
|
386
685
|
|
|
686
|
+
- ✅ Client-side compression
|
|
387
687
|
|
|
688
|
+
- ✅ WebP/WebM detection
|
|
388
689
|
|
|
690
|
+
- ✅ SSR/SSG compatibility
|
|
389
691
|
|
|
692
|
+
- ✅ TypeScript support
|
|
390
693
|
|
|
694
|
+
|
|
391
695
|
|
|
696
|
+
## 📄 License
|
|
392
697
|
|
|
698
|
+
|
|
393
699
|
|
|
700
|
+
MIT © 2026 Yared Abebe
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-media-optimizer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Drop-in React component for auto-optimized images & media with lazy loading, WebP conversion, and performance optimization",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|