perspectapi-ts-sdk 6.5.9 → 7.0.0
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 +46 -1011
- package/dist/chunk-K3T2AFYA.mjs +1393 -0
- package/dist/index-CWvUyMt3.d.mts +2224 -0
- package/dist/index-CWvUyMt3.d.ts +2224 -0
- package/dist/index.d.mts +130 -2221
- package/dist/index.d.ts +130 -2221
- package/dist/index.js +8 -2
- package/dist/index.mjs +13 -1364
- package/dist/v2/index.d.mts +1 -0
- package/dist/v2/index.d.ts +1 -0
- package/dist/v2/index.js +1419 -0
- package/dist/v2/index.mjs +40 -0
- package/docs/README.md +15 -0
- package/docs/v1-deprecated/README.md +9 -0
- package/docs/v1-deprecated/examples/README.md +324 -0
- package/docs/v1-deprecated/examples/basic-usage.ts +258 -0
- package/docs/v1-deprecated/examples/cloudflare-worker.ts +274 -0
- package/docs/v1-deprecated/examples/content-query-with-slug-prefix.ts +237 -0
- package/docs/v1-deprecated/examples/image-transforms.ts +200 -0
- package/docs/v1-deprecated/examples/site-user-checkout.ts +186 -0
- package/docs/v1-deprecated/examples/slug-prefix-examples.ts +491 -0
- package/docs/v1-deprecated/legacy-docs/caching.md +667 -0
- package/docs/v1-deprecated/legacy-docs/contact.md +1396 -0
- package/docs/v1-deprecated/legacy-docs/csrf-protection.md +664 -0
- package/docs/v1-deprecated/legacy-docs/image-transforms.md +523 -0
- package/docs/v1-deprecated/legacy-docs/loaders.md +304 -0
- package/docs/v1-deprecated/legacy-docs/newsletter.md +811 -0
- package/docs/v1-deprecated/legacy-docs/site-users.md +817 -0
- package/docs/v1-deprecated/legacy-notes/CHANGELOG-CHECKOUT.md +143 -0
- package/docs/v1-deprecated/legacy-notes/CSRF-CHECKOUT.md +271 -0
- package/docs/v1-deprecated/legacy-notes/IMAGE_TRANSFORMS_PORT.md +298 -0
- package/docs/v1-deprecated/sdk-readme.md +1076 -0
- package/examples/README.md +19 -0
- package/examples/basic-v2.ts +37 -0
- package/llms.txt +25 -0
- package/package.json +18 -7
- package/src/client/api-keys-client.ts +4 -0
- package/src/client/auth-client.ts +4 -0
- package/src/client/base-client.ts +7 -0
- package/src/client/bundles-client.ts +4 -0
- package/src/client/categories-client.ts +4 -0
- package/src/client/checkout-client.ts +4 -0
- package/src/client/contact-client.ts +4 -0
- package/src/client/content-client.ts +4 -0
- package/src/client/newsletter-client.ts +4 -0
- package/src/client/newsletter-management-client.ts +4 -0
- package/src/client/organizations-client.ts +4 -0
- package/src/client/products-client.ts +4 -0
- package/src/client/site-users-client.ts +10 -1
- package/src/client/sites-client.ts +4 -0
- package/src/client/webhooks-client.ts +4 -0
- package/src/deprecation.ts +2 -1
- package/src/index.ts +2 -1
- package/src/loaders.ts +59 -0
- package/src/perspect-api-client.ts +2 -2
- package/src/v2/client/orders-client.ts +6 -1
- package/src/v2/types.ts +3 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
# Image Transformations with Cloudflare Image Resizing
|
|
2
|
+
|
|
3
|
+
> Deprecated v1 material. Do not copy these examples into new code. v1 sunsets
|
|
4
|
+
> on 2026-06-01; use `createPerspectApiV2Client` from
|
|
5
|
+
> `perspectapi-ts-sdk/v2` and `/api/v2`.
|
|
6
|
+
|
|
7
|
+
The SDK provides utilities to easily transform images using Cloudflare's Image Resizing service. This allows you to resize, optimize, and transform images on-the-fly without pre-generating thumbnails.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
**Benefits:**
|
|
12
|
+
- ✅ **No pre-generated thumbnails** - Images are resized on demand
|
|
13
|
+
- ✅ **Automatic format optimization** - Serves WebP/AVIF when supported
|
|
14
|
+
- ✅ **Responsive images** - Browser requests the size it needs
|
|
15
|
+
- ✅ **CDN caching** - Transformed images are cached at the edge
|
|
16
|
+
- ✅ **Bandwidth savings** - Only serve the size needed
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import {
|
|
22
|
+
buildImageUrl,
|
|
23
|
+
generateResponsiveUrls,
|
|
24
|
+
transformMediaItem
|
|
25
|
+
} from 'perspectapi-ts-sdk';
|
|
26
|
+
|
|
27
|
+
// Get a product with media
|
|
28
|
+
const product = await client.products.getProduct('mysite', 123);
|
|
29
|
+
const media = product.data.media?.[0];
|
|
30
|
+
|
|
31
|
+
if (media) {
|
|
32
|
+
// Generate all responsive sizes
|
|
33
|
+
const urls = transformMediaItem('https://api.perspect.comm', media);
|
|
34
|
+
|
|
35
|
+
console.log(urls.thumbnail); // 150x150 cover crop
|
|
36
|
+
console.log(urls.small); // 400px wide
|
|
37
|
+
console.log(urls.medium); // 800px wide
|
|
38
|
+
console.log(urls.large); // 1200px wide
|
|
39
|
+
console.log(urls.original); // Original with auto format
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Core Functions
|
|
44
|
+
|
|
45
|
+
### `buildImageUrl()`
|
|
46
|
+
|
|
47
|
+
Build a single transformed image URL with custom options.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { buildImageUrl } from 'perspectapi-ts-sdk';
|
|
51
|
+
|
|
52
|
+
const url = buildImageUrl(
|
|
53
|
+
'https://api.perspect.comm',
|
|
54
|
+
'media/mysite/photo.jpg',
|
|
55
|
+
{
|
|
56
|
+
width: 400,
|
|
57
|
+
height: 300,
|
|
58
|
+
fit: 'cover',
|
|
59
|
+
quality: 85,
|
|
60
|
+
format: 'webp'
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Parameters:**
|
|
66
|
+
- `baseUrl` - Your API base URL (e.g., `'https://api.perspect.comm'`)
|
|
67
|
+
- `mediaPath` - Path to the media file (e.g., `'media/mysite/photo.jpg'`)
|
|
68
|
+
- `options` - Transform options (optional)
|
|
69
|
+
|
|
70
|
+
**Transform Options:**
|
|
71
|
+
|
|
72
|
+
| Option | Type | Values | Description |
|
|
73
|
+
|--------|------|--------|-------------|
|
|
74
|
+
| `width` | number | 1-9999 | Target width in pixels |
|
|
75
|
+
| `height` | number | 1-9999 | Target height in pixels |
|
|
76
|
+
| `fit` | string | `scale-down`, `contain`, `cover`, `crop`, `pad` | Resize mode |
|
|
77
|
+
| `gravity` | string | `auto`, `left`, `right`, `top`, `bottom`, `center` | Crop focus point |
|
|
78
|
+
| `quality` | number | 1-100 | JPEG/WebP quality (default: 85) |
|
|
79
|
+
| `format` | string | `auto`, `avif`, `webp`, `jpeg`, `png` | Output format |
|
|
80
|
+
| `dpr` | number | 1, 2, 3 | Device pixel ratio |
|
|
81
|
+
| `sharpen` | number | 0-10 | Sharpening amount |
|
|
82
|
+
| `blur` | number | 0-250 | Blur amount |
|
|
83
|
+
| `rotate` | number | 0, 90, 180, 270 | Rotation degrees |
|
|
84
|
+
|
|
85
|
+
### `generateResponsiveUrls()`
|
|
86
|
+
|
|
87
|
+
Generate multiple sizes at once for responsive images.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { generateResponsiveUrls } from 'perspectapi-ts-sdk';
|
|
91
|
+
|
|
92
|
+
const urls = generateResponsiveUrls(
|
|
93
|
+
'https://api.perspect.comm',
|
|
94
|
+
'media/mysite/photo.jpg'
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Returns:
|
|
98
|
+
// {
|
|
99
|
+
// thumbnail: '...?width=150&height=150&fit=cover',
|
|
100
|
+
// small: '...?width=400',
|
|
101
|
+
// medium: '...?width=800',
|
|
102
|
+
// large: '...?width=1200',
|
|
103
|
+
// original: '...'
|
|
104
|
+
// }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `transformMediaItem()`
|
|
108
|
+
|
|
109
|
+
Convenience function for working with `MediaItem` objects from the API.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { transformMediaItem } from 'perspectapi-ts-sdk';
|
|
113
|
+
|
|
114
|
+
// From a product
|
|
115
|
+
const product = await client.products.getProduct('mysite', 123);
|
|
116
|
+
const media = product.data.media?.[0];
|
|
117
|
+
|
|
118
|
+
if (media) {
|
|
119
|
+
const urls = transformMediaItem('https://api.perspect.comm', media);
|
|
120
|
+
// Use urls.thumbnail, urls.small, etc.
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// From content
|
|
124
|
+
const content = await client.content.getContent('mysite', { limit: 10 });
|
|
125
|
+
const firstPost = content.data?.[0];
|
|
126
|
+
// Extract media from content and transform similarly
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `generateSrcSet()`
|
|
130
|
+
|
|
131
|
+
Generate `srcset` attribute for responsive images.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { generateSrcSet } from 'perspectapi-ts-sdk';
|
|
135
|
+
|
|
136
|
+
const srcset = generateSrcSet(
|
|
137
|
+
'https://api.perspect.comm',
|
|
138
|
+
'media/mysite/photo.jpg',
|
|
139
|
+
[400, 800, 1200, 1600]
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Returns: "/cdn-cgi/image/width=400.../photo.jpg 400w, /cdn-cgi/image/width=800.../photo.jpg 800w, ..."
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Use in HTML:
|
|
146
|
+
|
|
147
|
+
```html
|
|
148
|
+
<img
|
|
149
|
+
src="/cdn-cgi/image/width=800.../photo.jpg"
|
|
150
|
+
srcset="..."
|
|
151
|
+
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 60vw"
|
|
152
|
+
alt="Photo"
|
|
153
|
+
/>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### `generateResponsiveImageHtml()`
|
|
157
|
+
|
|
158
|
+
Generate complete responsive image HTML.
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { generateResponsiveImageHtml } from 'perspectapi-ts-sdk';
|
|
162
|
+
|
|
163
|
+
const html = generateResponsiveImageHtml(
|
|
164
|
+
'https://api.perspect.comm',
|
|
165
|
+
'media/mysite/photo.jpg',
|
|
166
|
+
'My photo',
|
|
167
|
+
{
|
|
168
|
+
className: 'rounded-lg shadow-md',
|
|
169
|
+
loading: 'lazy',
|
|
170
|
+
widths: [400, 800, 1200]
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Returns complete <img> tag with srcset, sizes, etc.
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Common Use Cases
|
|
178
|
+
|
|
179
|
+
### 1. Product Images (E-commerce)
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { createPerspectApiClient, transformMediaItem } from 'perspectapi-ts-sdk';
|
|
183
|
+
|
|
184
|
+
const client = createPerspectApiClient({
|
|
185
|
+
baseUrl: 'https://api.perspect.comm',
|
|
186
|
+
apiKey: 'your-api-key'
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Get products
|
|
190
|
+
const products = await client.products.getProducts('mysite', {
|
|
191
|
+
limit: 20,
|
|
192
|
+
published: true
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Transform product images
|
|
196
|
+
const productsWithImages = products.data.map(product => {
|
|
197
|
+
const media = product.media?.[0];
|
|
198
|
+
const imageUrls = media
|
|
199
|
+
? transformMediaItem('https://api.perspect.comm', media)
|
|
200
|
+
: null;
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
...product,
|
|
204
|
+
imageUrls
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Use in your UI
|
|
209
|
+
productsWithImages.forEach(product => {
|
|
210
|
+
console.log(`Thumbnail: ${product.imageUrls?.thumbnail}`);
|
|
211
|
+
console.log(`Large: ${product.imageUrls?.large}`);
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 2. Blog Post Featured Images
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import { loadPosts, buildImageUrl } from 'perspectapi-ts-sdk';
|
|
219
|
+
|
|
220
|
+
const posts = await loadPosts({
|
|
221
|
+
client,
|
|
222
|
+
siteName: 'myblog',
|
|
223
|
+
limit: 10
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
posts.forEach(post => {
|
|
227
|
+
if (post.featured_image) {
|
|
228
|
+
const featuredImage = buildImageUrl(
|
|
229
|
+
'https://api.perspect.comm',
|
|
230
|
+
post.featured_image,
|
|
231
|
+
{
|
|
232
|
+
width: 800,
|
|
233
|
+
fit: 'scale-down',
|
|
234
|
+
format: 'auto',
|
|
235
|
+
quality: 85
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
console.log(`Featured image: ${featuredImage}`);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 3. Responsive Image Component (React)
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
import { transformMediaItem, type MediaItem } from 'perspectapi-ts-sdk';
|
|
247
|
+
|
|
248
|
+
interface ResponsiveImageProps {
|
|
249
|
+
media: MediaItem;
|
|
250
|
+
alt: string;
|
|
251
|
+
className?: string;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function ResponsiveImage({ media, alt, className }: ResponsiveImageProps) {
|
|
255
|
+
const urls = transformMediaItem('https://api.perspect.comm', media);
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<img
|
|
259
|
+
src={urls.medium}
|
|
260
|
+
srcSet={`
|
|
261
|
+
${urls.small} 400w,
|
|
262
|
+
${urls.medium} 800w,
|
|
263
|
+
${urls.large} 1200w
|
|
264
|
+
`}
|
|
265
|
+
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 60vw"
|
|
266
|
+
alt={alt}
|
|
267
|
+
className={className}
|
|
268
|
+
loading="lazy"
|
|
269
|
+
decoding="async"
|
|
270
|
+
/>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Usage
|
|
275
|
+
<ResponsiveImage
|
|
276
|
+
media={product.media[0]}
|
|
277
|
+
alt={product.name}
|
|
278
|
+
className="rounded-lg"
|
|
279
|
+
/>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### 4. Custom Sizes
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
import { generateResponsiveUrls, type ResponsiveImageSizes } from 'perspectapi-ts-sdk';
|
|
286
|
+
|
|
287
|
+
// Define custom sizes for your use case
|
|
288
|
+
const customSizes: ResponsiveImageSizes = {
|
|
289
|
+
thumbnail: {
|
|
290
|
+
width: 100,
|
|
291
|
+
height: 100,
|
|
292
|
+
fit: 'cover',
|
|
293
|
+
quality: 75,
|
|
294
|
+
format: 'auto'
|
|
295
|
+
},
|
|
296
|
+
small: {
|
|
297
|
+
width: 300,
|
|
298
|
+
fit: 'scale-down',
|
|
299
|
+
quality: 85,
|
|
300
|
+
format: 'auto'
|
|
301
|
+
},
|
|
302
|
+
medium: {
|
|
303
|
+
width: 600,
|
|
304
|
+
fit: 'scale-down',
|
|
305
|
+
quality: 85,
|
|
306
|
+
format: 'auto'
|
|
307
|
+
},
|
|
308
|
+
large: {
|
|
309
|
+
width: 1000,
|
|
310
|
+
fit: 'scale-down',
|
|
311
|
+
quality: 85,
|
|
312
|
+
format: 'auto'
|
|
313
|
+
},
|
|
314
|
+
original: {
|
|
315
|
+
format: 'auto'
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const urls = generateResponsiveUrls(
|
|
320
|
+
'https://api.perspect.comm',
|
|
321
|
+
'media/mysite/photo.jpg',
|
|
322
|
+
customSizes
|
|
323
|
+
);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### 5. High DPR (Retina) Displays
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { buildImageUrl } from 'perspectapi-ts-sdk';
|
|
330
|
+
|
|
331
|
+
// For retina displays
|
|
332
|
+
const retinaUrl = buildImageUrl(
|
|
333
|
+
'https://api.perspect.comm',
|
|
334
|
+
'media/mysite/photo.jpg',
|
|
335
|
+
{
|
|
336
|
+
width: 400,
|
|
337
|
+
dpr: 2, // 2x resolution
|
|
338
|
+
format: 'auto',
|
|
339
|
+
quality: 85
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Framework Examples
|
|
345
|
+
|
|
346
|
+
### Next.js App Router
|
|
347
|
+
|
|
348
|
+
```tsx
|
|
349
|
+
// app/products/[slug]/page.tsx
|
|
350
|
+
import { createPerspectApiClient, transformMediaItem } from 'perspectapi-ts-sdk';
|
|
351
|
+
import Image from 'next/image';
|
|
352
|
+
|
|
353
|
+
const client = createPerspectApiClient({
|
|
354
|
+
baseUrl: process.env.NEXT_PUBLIC_API_URL!,
|
|
355
|
+
apiKey: process.env.API_KEY!
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
export default async function ProductPage({ params }: { params: { slug: string } }) {
|
|
359
|
+
const product = await client.products.getProductBySlug('mysite', params.slug);
|
|
360
|
+
const media = product.data.media?.[0];
|
|
361
|
+
|
|
362
|
+
if (!media) return <div>No image</div>;
|
|
363
|
+
|
|
364
|
+
const urls = transformMediaItem(process.env.NEXT_PUBLIC_API_URL!, media);
|
|
365
|
+
|
|
366
|
+
return (
|
|
367
|
+
<div>
|
|
368
|
+
<Image
|
|
369
|
+
src={urls.large}
|
|
370
|
+
alt={product.data.name || ''}
|
|
371
|
+
width={1200}
|
|
372
|
+
height={800}
|
|
373
|
+
className="rounded-lg"
|
|
374
|
+
/>
|
|
375
|
+
</div>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Remix Loader
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
// app/routes/products.$slug.tsx
|
|
384
|
+
import { json, type LoaderFunctionArgs } from '@remix-run/node';
|
|
385
|
+
import { useLoaderData } from '@remix-run/react';
|
|
386
|
+
import { createPerspectApiClient, transformMediaItem } from 'perspectapi-ts-sdk';
|
|
387
|
+
|
|
388
|
+
export async function loader({ params }: LoaderFunctionArgs) {
|
|
389
|
+
const client = createPerspectApiClient({
|
|
390
|
+
baseUrl: process.env.API_URL!,
|
|
391
|
+
apiKey: process.env.API_KEY!
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const product = await client.products.getProductBySlug('mysite', params.slug!);
|
|
395
|
+
const media = product.data.media?.[0];
|
|
396
|
+
|
|
397
|
+
const imageUrls = media
|
|
398
|
+
? transformMediaItem(process.env.API_URL!, media)
|
|
399
|
+
: null;
|
|
400
|
+
|
|
401
|
+
return json({ product: product.data, imageUrls });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export default function ProductPage() {
|
|
405
|
+
const { product, imageUrls } = useLoaderData<typeof loader>();
|
|
406
|
+
|
|
407
|
+
return (
|
|
408
|
+
<div>
|
|
409
|
+
{imageUrls && (
|
|
410
|
+
<img
|
|
411
|
+
src={imageUrls.medium}
|
|
412
|
+
srcSet={`
|
|
413
|
+
${imageUrls.small} 400w,
|
|
414
|
+
${imageUrls.medium} 800w,
|
|
415
|
+
${imageUrls.large} 1200w
|
|
416
|
+
`}
|
|
417
|
+
sizes="(max-width: 640px) 100vw, 800px"
|
|
418
|
+
alt={product.name}
|
|
419
|
+
loading="lazy"
|
|
420
|
+
/>
|
|
421
|
+
)}
|
|
422
|
+
</div>
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Cloudflare Workers
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import { createPerspectApiClient, transformMediaItem } from 'perspectapi-ts-sdk';
|
|
431
|
+
|
|
432
|
+
export default {
|
|
433
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
434
|
+
const client = createPerspectApiClient({
|
|
435
|
+
baseUrl: env.API_URL,
|
|
436
|
+
apiKey: env.API_KEY
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
const products = await client.products.getProducts('mysite', {
|
|
440
|
+
limit: 10,
|
|
441
|
+
published: true
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
const productsWithImages = products.data.map(product => {
|
|
445
|
+
const media = product.media?.[0];
|
|
446
|
+
const imageUrls = media
|
|
447
|
+
? transformMediaItem(env.API_URL, media)
|
|
448
|
+
: null;
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
id: product.id,
|
|
452
|
+
name: product.name,
|
|
453
|
+
price: product.price,
|
|
454
|
+
thumbnail: imageUrls?.thumbnail,
|
|
455
|
+
image: imageUrls?.medium
|
|
456
|
+
};
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
return Response.json(productsWithImages);
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
## Best Practices
|
|
465
|
+
|
|
466
|
+
1. **Always use `format=auto`** - Let Cloudflare choose the best format (WebP, AVIF, etc.)
|
|
467
|
+
2. **Set appropriate quality** - 85 is a good default, 75 for thumbnails
|
|
468
|
+
3. **Use `fit=scale-down`** - Prevents upscaling small images
|
|
469
|
+
4. **Add `loading=lazy`** - Improves page load performance
|
|
470
|
+
5. **Use srcset for responsive** - Let browser choose appropriate size
|
|
471
|
+
6. **Cache aggressively** - Images rarely change, use long cache times
|
|
472
|
+
|
|
473
|
+
## Performance Benefits
|
|
474
|
+
|
|
475
|
+
### Storage Savings
|
|
476
|
+
- **Before**: 5 sizes × file size = 5× storage
|
|
477
|
+
- **After**: 1 original file only
|
|
478
|
+
|
|
479
|
+
### Upload Time
|
|
480
|
+
- **Before**: Generate all sizes on upload
|
|
481
|
+
- **After**: Store original only
|
|
482
|
+
|
|
483
|
+
### Flexibility
|
|
484
|
+
- **Before**: Fixed sizes, must regenerate to change
|
|
485
|
+
- **After**: Any size on demand
|
|
486
|
+
|
|
487
|
+
### Caching
|
|
488
|
+
- First request: Cloudflare resizes and caches
|
|
489
|
+
- Subsequent requests: Served from edge cache (1 year TTL)
|
|
490
|
+
- Global CDN: Fast delivery worldwide
|
|
491
|
+
|
|
492
|
+
## Requirements
|
|
493
|
+
|
|
494
|
+
- Cloudflare Image Resizing must be enabled on your zone
|
|
495
|
+
- Images must be served through Cloudflare
|
|
496
|
+
- Free tier: 100,000 images/month
|
|
497
|
+
- Paid: $5 per 100,000 images after free tier
|
|
498
|
+
|
|
499
|
+
## Troubleshooting
|
|
500
|
+
|
|
501
|
+
### Images not transforming?
|
|
502
|
+
|
|
503
|
+
Check that:
|
|
504
|
+
- The URL includes the `/cdn-cgi/image/` prefix
|
|
505
|
+
- The source image is accessible
|
|
506
|
+
- Cloudflare Image Resizing is enabled on your zone
|
|
507
|
+
|
|
508
|
+
### Images loading slowly?
|
|
509
|
+
|
|
510
|
+
- First request is slower (resize + cache)
|
|
511
|
+
- Subsequent requests are fast (served from cache)
|
|
512
|
+
- Consider preloading critical images
|
|
513
|
+
|
|
514
|
+
### Wrong format served?
|
|
515
|
+
|
|
516
|
+
- Use `format=auto` to let Cloudflare decide
|
|
517
|
+
- Browser must support the format (check Accept header)
|
|
518
|
+
|
|
519
|
+
## Resources
|
|
520
|
+
|
|
521
|
+
- [Cloudflare Image Resizing Docs](https://developers.cloudflare.com/images/transform-images/)
|
|
522
|
+
- [Responsive Images Guide](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)
|
|
523
|
+
- [WebP Format](https://developers.google.com/speed/webp)
|