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