create-cloudinary-react 1.0.0-beta.1
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/.github/CODEOWNERS +4 -0
- package/.github/workflows/release.yml +45 -0
- package/.husky/commit-msg +1 -0
- package/.releaserc.json +23 -0
- package/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/cli.js +283 -0
- package/commitlint.config.js +23 -0
- package/package.json +46 -0
- package/templates/.cursor/mcp.json.template +10 -0
- package/templates/.cursorrules.template +525 -0
- package/templates/.env.example.template +10 -0
- package/templates/.env.template +5 -0
- package/templates/.gitignore.template +29 -0
- package/templates/README.md.template +46 -0
- package/templates/eslint.config.js.template +23 -0
- package/templates/index.html.template +13 -0
- package/templates/package.json.template +32 -0
- package/templates/src/App.css.template +94 -0
- package/templates/src/App.tsx.template +80 -0
- package/templates/src/cloudinary/UploadWidget.tsx.template +112 -0
- package/templates/src/cloudinary/config.ts.template +21 -0
- package/templates/src/index.css.template +29 -0
- package/templates/src/main.tsx.template +10 -0
- package/templates/tsconfig.app.json.template +24 -0
- package/templates/tsconfig.json.template +7 -0
- package/templates/tsconfig.node.json.template +22 -0
- package/templates/vite.config.ts.template +7 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
# Cloudinary React SDK Patterns & Common Errors
|
|
2
|
+
|
|
3
|
+
## Official Documentation
|
|
4
|
+
- **Transformation Rules**: https://cloudinary.com/documentation/cloudinary_transformation_rules.md
|
|
5
|
+
- **Transformation Reference**: https://cloudinary.com/documentation/transformation_reference
|
|
6
|
+
- **React Image Transformations & Plugins**: https://cloudinary.com/documentation/react_image_transformations#plugins
|
|
7
|
+
- **React Video Transformations**: https://cloudinary.com/documentation/react_video_transformations
|
|
8
|
+
- **Cloudinary Video Player** (standalone player): https://cloudinary.com/documentation/cloudinary_video_player
|
|
9
|
+
- **Video Player React Tutorial**: https://cloudinary.com/documentation/video_player_react_tutorial#banner
|
|
10
|
+
- Always consult the official transformation rules when creating transformations
|
|
11
|
+
- Use only officially supported parameters from the transformation reference
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# 📋 PATTERNS (How to Do It Right)
|
|
16
|
+
|
|
17
|
+
## Environment Variables
|
|
18
|
+
- **Vite requires VITE_ prefix** - Environment variables MUST start with `VITE_` to be exposed to the browser
|
|
19
|
+
- ✅ CORRECT: `VITE_CLOUDINARY_CLOUD_NAME=mycloud` in `.env` file
|
|
20
|
+
- ✅ CORRECT: `import.meta.env.VITE_CLOUDINARY_CLOUD_NAME` (not `process.env`)
|
|
21
|
+
- Always restart dev server after adding/updating `.env` variables
|
|
22
|
+
|
|
23
|
+
## Upload Presets
|
|
24
|
+
- **Unsigned upload presets are required for client-side uploads** - Transformations work without them, but uploads need an unsigned preset
|
|
25
|
+
- ✅ Create unsigned upload preset: https://console.cloudinary.com/app/settings/upload/presets
|
|
26
|
+
- ✅ Set preset in `.env`: `VITE_CLOUDINARY_UPLOAD_PRESET=your-preset-name`
|
|
27
|
+
- ✅ Use in code: `import { uploadPreset } from './cloudinary/config'`
|
|
28
|
+
- ⚠️ If upload preset is missing, the Upload Widget will show an error message
|
|
29
|
+
- ⚠️ Upload presets must be set to "Unsigned" mode for client-side usage (no API key/secret needed)
|
|
30
|
+
|
|
31
|
+
## Import Patterns
|
|
32
|
+
- ✅ Import Cloudinary instance: `import { cld } from './cloudinary/config'`
|
|
33
|
+
- ✅ Import components: `import { AdvancedImage, AdvancedVideo } from '@cloudinary/react'`
|
|
34
|
+
- ✅ Import transformations: `import { fill } from '@cloudinary/url-gen/actions/resize'`
|
|
35
|
+
- ✅ Import effects: `import { blur } from '@cloudinary/url-gen/actions/effect'`
|
|
36
|
+
- ✅ Import delivery: `import { format, quality } from '@cloudinary/url-gen/actions/delivery'`
|
|
37
|
+
- ✅ Import qualifiers: `import { auto } from '@cloudinary/url-gen/qualifiers/format'`
|
|
38
|
+
- ✅ Import qualifiers: `import { auto as autoQuality } from '@cloudinary/url-gen/qualifiers/quality'`
|
|
39
|
+
- ✅ Import plugins: `import { responsive, lazyload, placeholder } from '@cloudinary/react'`
|
|
40
|
+
|
|
41
|
+
## Creating Image & Video Instances
|
|
42
|
+
- ✅ Create image instance: `const img = cld.image(publicId)`
|
|
43
|
+
- ✅ Create video instance: `const video = cld.video(publicId)` (same pattern as images)
|
|
44
|
+
- ✅ Public ID format: Use forward slashes for folders (e.g., `'folder/subfolder/image'`)
|
|
45
|
+
- ✅ Public IDs are case-sensitive and should not include file extensions
|
|
46
|
+
- ✅ Examples:
|
|
47
|
+
```tsx
|
|
48
|
+
const displayImage = cld.image('samples/cloudinary-icon');
|
|
49
|
+
const displayVideo = cld.video('samples/elephants');
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Transformation Patterns
|
|
53
|
+
|
|
54
|
+
### Image Transformations
|
|
55
|
+
- ✅ Chain transformations on image instance:
|
|
56
|
+
```tsx
|
|
57
|
+
const img = cld.image('id')
|
|
58
|
+
.resize(fill().width(800).height(600))
|
|
59
|
+
.effect(blur(800))
|
|
60
|
+
.delivery(format(auto()))
|
|
61
|
+
.delivery(quality(autoQuality()));
|
|
62
|
+
```
|
|
63
|
+
- ✅ Pass to component: `<AdvancedImage cldImg={img} />`
|
|
64
|
+
|
|
65
|
+
### Video Transformations
|
|
66
|
+
- ✅ Chain transformations on video instance (same pattern as images):
|
|
67
|
+
```tsx
|
|
68
|
+
const video = cld.video('id')
|
|
69
|
+
.resize(fill().width(800).height(600))
|
|
70
|
+
.delivery(format(auto()));
|
|
71
|
+
```
|
|
72
|
+
- ✅ Pass to component: `<AdvancedVideo cldVid={video} />`
|
|
73
|
+
- ✅ Video transformations work the same way as image transformations
|
|
74
|
+
|
|
75
|
+
### Transformation Best Practices
|
|
76
|
+
- ✅ Format and quality must use separate `.delivery()` calls
|
|
77
|
+
- ✅ Always end with auto format/quality: `.delivery(format(auto())).delivery(quality(autoQuality()))`
|
|
78
|
+
- ✅ Use `gravity(auto())` unless user specifies a focal point
|
|
79
|
+
- ✅ Same transformation syntax works for both images and videos
|
|
80
|
+
|
|
81
|
+
## Plugin Patterns
|
|
82
|
+
- ✅ Import plugins from `@cloudinary/react`
|
|
83
|
+
- ✅ Pass plugins as array: `plugins={[responsive(), lazyload(), placeholder()]}`
|
|
84
|
+
- ✅ Recommended plugin order:
|
|
85
|
+
1. `responsive()` - First
|
|
86
|
+
2. `lazyload()` - Second
|
|
87
|
+
3. `accessibility()` - Third
|
|
88
|
+
4. `placeholder()` - Last
|
|
89
|
+
- ✅ Always add `width` and `height` attributes to prevent layout shift
|
|
90
|
+
- ✅ Example:
|
|
91
|
+
```tsx
|
|
92
|
+
<AdvancedImage
|
|
93
|
+
cldImg={img}
|
|
94
|
+
plugins={[responsive(), lazyload(), placeholder({ mode: 'blur' })]}
|
|
95
|
+
width={800}
|
|
96
|
+
height={600}
|
|
97
|
+
/>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Responsive Images Pattern
|
|
101
|
+
- ✅ Use `responsive()` plugin with `fill()` resize
|
|
102
|
+
- ✅ Combine with `placeholder()` and `lazyload()` plugins
|
|
103
|
+
- ✅ Example:
|
|
104
|
+
```tsx
|
|
105
|
+
const img = cld.image('id').resize(fill().width(800));
|
|
106
|
+
<AdvancedImage
|
|
107
|
+
cldImg={img}
|
|
108
|
+
plugins={[responsive(), placeholder({ mode: 'blur' }), lazyload()]}
|
|
109
|
+
width={800}
|
|
110
|
+
height={600}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Upload Widget Pattern
|
|
115
|
+
- ✅ Use component: `import { UploadWidget } from './cloudinary/UploadWidget'`
|
|
116
|
+
- ✅ Load script in `index.html`:
|
|
117
|
+
```html
|
|
118
|
+
<script src="https://upload-widget.cloudinary.com/global/all.js" async></script>
|
|
119
|
+
```
|
|
120
|
+
- ✅ Create unsigned upload preset in dashboard at `settings/upload/presets`
|
|
121
|
+
- ✅ Add to `.env`: `VITE_CLOUDINARY_UPLOAD_PRESET=your_preset_name`
|
|
122
|
+
- ✅ Handle callbacks:
|
|
123
|
+
```tsx
|
|
124
|
+
<UploadWidget
|
|
125
|
+
onUploadSuccess={(result) => {
|
|
126
|
+
console.log('Public ID:', result.public_id);
|
|
127
|
+
}}
|
|
128
|
+
onUploadError={(error) => {
|
|
129
|
+
console.error('Upload failed:', error);
|
|
130
|
+
}}
|
|
131
|
+
/>
|
|
132
|
+
```
|
|
133
|
+
- ✅ Upload result contains: `public_id`, `secure_url`, `width`, `height`, etc.
|
|
134
|
+
|
|
135
|
+
## Video Patterns
|
|
136
|
+
|
|
137
|
+
### ⚠️ IMPORTANT: Two Different Approaches
|
|
138
|
+
|
|
139
|
+
**1. AdvancedVideo Component** (`@cloudinary/react`) - For video transformations
|
|
140
|
+
- React component similar to `AdvancedImage`
|
|
141
|
+
- Use for displaying videos with Cloudinary transformations
|
|
142
|
+
- Works with `cld.video()` like images work with `cld.image()`
|
|
143
|
+
- Simple, React-friendly approach
|
|
144
|
+
|
|
145
|
+
**2. Cloudinary Video Player** (`cloudinary-video-player`) - For advanced player features
|
|
146
|
+
- Standalone video player library
|
|
147
|
+
- Use for advanced features: playlists, recommendations, ads, chapters, etc.
|
|
148
|
+
- Full-featured player with analytics, monetization, etc.
|
|
149
|
+
- More complex setup, but more powerful
|
|
150
|
+
|
|
151
|
+
### AdvancedVideo Component (React SDK - For Transformations)
|
|
152
|
+
- ✅ **Purpose**: Display videos with Cloudinary transformations (resize, effects, etc.)
|
|
153
|
+
- ✅ **Package**: `@cloudinary/react` (same as AdvancedImage)
|
|
154
|
+
- ✅ **Import**: `import { AdvancedVideo } from '@cloudinary/react'`
|
|
155
|
+
- ✅ **NO CSS IMPORT NEEDED**: AdvancedVideo uses native HTML5 video - no CSS import required
|
|
156
|
+
- ❌ **WRONG**: `import '@cloudinary/react/dist/cld-video-player.css'` (this path doesn't exist)
|
|
157
|
+
- ✅ **Create video instance**: `const video = cld.video(publicId)` (like `cld.image()`)
|
|
158
|
+
- ✅ **Apply transformations**: Chain transformations like images:
|
|
159
|
+
```tsx
|
|
160
|
+
const video = cld.video('video-id')
|
|
161
|
+
.resize(fill().width(800).height(600))
|
|
162
|
+
.delivery(format(auto()));
|
|
163
|
+
```
|
|
164
|
+
- ✅ **Use component**:
|
|
165
|
+
```tsx
|
|
166
|
+
<AdvancedVideo
|
|
167
|
+
cldVid={video}
|
|
168
|
+
controls
|
|
169
|
+
autoplay
|
|
170
|
+
muted
|
|
171
|
+
/>
|
|
172
|
+
```
|
|
173
|
+
- ✅ **Documentation**: https://cloudinary.com/documentation/react_video_transformations
|
|
174
|
+
|
|
175
|
+
### Cloudinary Video Player (Standalone - For Advanced Features)
|
|
176
|
+
- ✅ **Purpose**: Full-featured video player with playlists, recommendations, ads, etc.
|
|
177
|
+
- ✅ **Package**: `cloudinary-video-player` (separate package)
|
|
178
|
+
- ✅ **Import**: `import cloudinary from 'cloudinary-video-player'`
|
|
179
|
+
- ✅ **Import CSS**: `import 'cloudinary-video-player/dist/cld-video-player.css'`
|
|
180
|
+
- ✅ **Import modules** (for advanced features):
|
|
181
|
+
```tsx
|
|
182
|
+
import 'cloudinary-video-player/dist/adaptive-streaming'; // HLS/DASH
|
|
183
|
+
import 'cloudinary-video-player/dist/playlist'; // Playlists
|
|
184
|
+
import 'cloudinary-video-player/dist/recommendations-overlay'; // Recommendations
|
|
185
|
+
// Or import all: import 'cloudinary-video-player/dist/all'
|
|
186
|
+
```
|
|
187
|
+
- ✅ **Initialize in useEffect** with cleanup:
|
|
188
|
+
```tsx
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
const player = cloudinary.videoPlayer(ref.current, {
|
|
191
|
+
cloud_name: import.meta.env.VITE_CLOUDINARY_CLOUD_NAME,
|
|
192
|
+
secure: true,
|
|
193
|
+
controls: true,
|
|
194
|
+
});
|
|
195
|
+
player.source(publicId);
|
|
196
|
+
return () => player.dispose(); // Always cleanup!
|
|
197
|
+
}, [publicId]);
|
|
198
|
+
```
|
|
199
|
+
- ✅ **Video element classes**: `className="cld-video-player cld-fluid"`
|
|
200
|
+
- ✅ **Documentation**: https://cloudinary.com/documentation/cloudinary_video_player
|
|
201
|
+
- ✅ **React Tutorial**: https://cloudinary.com/documentation/video_player_react_tutorial#banner
|
|
202
|
+
|
|
203
|
+
### When to Use Which?
|
|
204
|
+
- ✅ **Use AdvancedVideo** when: You need simple video playback with transformations
|
|
205
|
+
- ✅ **Use Video Player** when: You need playlists, recommendations, ads, chapters, or other advanced features
|
|
206
|
+
|
|
207
|
+
## TypeScript Patterns
|
|
208
|
+
|
|
209
|
+
### Type Imports
|
|
210
|
+
- ✅ Import types from `@cloudinary/url-gen`:
|
|
211
|
+
```tsx
|
|
212
|
+
import type { CloudinaryImage } from '@cloudinary/url-gen';
|
|
213
|
+
import type { CloudinaryVideo } from '@cloudinary/url-gen';
|
|
214
|
+
```
|
|
215
|
+
- ✅ Type image instance: `const img: CloudinaryImage = cld.image('id')`
|
|
216
|
+
- ✅ Type video instance: `const video: CloudinaryVideo = cld.video('id')`
|
|
217
|
+
|
|
218
|
+
### Upload Result Types
|
|
219
|
+
- ✅ Define interface for upload results:
|
|
220
|
+
```tsx
|
|
221
|
+
interface CloudinaryUploadResult {
|
|
222
|
+
public_id: string;
|
|
223
|
+
secure_url: string;
|
|
224
|
+
url: string;
|
|
225
|
+
width: number;
|
|
226
|
+
height: number;
|
|
227
|
+
format: string;
|
|
228
|
+
resource_type: string;
|
|
229
|
+
bytes: number;
|
|
230
|
+
created_at: string;
|
|
231
|
+
// Add other fields as needed
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
- ✅ Type upload callbacks:
|
|
235
|
+
```tsx
|
|
236
|
+
onUploadSuccess?: (result: CloudinaryUploadResult) => void;
|
|
237
|
+
```
|
|
238
|
+
- ❌ **WRONG**: `onUploadSuccess?: (result: any) => void`
|
|
239
|
+
- ✅ **CORRECT**: Use proper interface or type definition
|
|
240
|
+
|
|
241
|
+
### Environment Variable Typing
|
|
242
|
+
- ✅ Create `vite-env.d.ts` for type safety:
|
|
243
|
+
```tsx
|
|
244
|
+
/// <reference types="vite/client" />
|
|
245
|
+
|
|
246
|
+
interface ImportMetaEnv {
|
|
247
|
+
readonly VITE_CLOUDINARY_CLOUD_NAME: string;
|
|
248
|
+
readonly VITE_CLOUDINARY_UPLOAD_PRESET?: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
interface ImportMeta {
|
|
252
|
+
readonly env: ImportMetaEnv;
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
- ✅ Access with type safety: `import.meta.env.VITE_CLOUDINARY_CLOUD_NAME`
|
|
256
|
+
|
|
257
|
+
### Type Guards and Safety
|
|
258
|
+
- ✅ Type guard for window.cloudinary:
|
|
259
|
+
```tsx
|
|
260
|
+
function isCloudinaryLoaded(): boolean {
|
|
261
|
+
return typeof window !== 'undefined' &&
|
|
262
|
+
typeof window.cloudinary !== 'undefined';
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
- ✅ Use type guards before accessing:
|
|
266
|
+
```tsx
|
|
267
|
+
if (isCloudinaryLoaded()) {
|
|
268
|
+
window.cloudinary.createUploadWidget(...);
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Ref Typing Patterns
|
|
273
|
+
- ✅ Type refs properly:
|
|
274
|
+
```tsx
|
|
275
|
+
// Video element ref
|
|
276
|
+
const videoRef = useRef<HTMLVideoElement>(null);
|
|
277
|
+
|
|
278
|
+
// Button ref
|
|
279
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
280
|
+
|
|
281
|
+
// Widget ref (use unknown if types not available)
|
|
282
|
+
const widgetRef = useRef<unknown>(null);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Type Narrowing
|
|
286
|
+
- ✅ Handle optional values with type narrowing:
|
|
287
|
+
```tsx
|
|
288
|
+
const preset = uploadPreset || undefined; // Type: string | undefined
|
|
289
|
+
|
|
290
|
+
// Type narrowing in conditionals
|
|
291
|
+
if (uploadPreset) {
|
|
292
|
+
// TypeScript knows uploadPreset is string here
|
|
293
|
+
console.log(preset.length);
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Avoid `any` Type
|
|
298
|
+
- ❌ **WRONG**: `const result: any = ...`
|
|
299
|
+
- ✅ **CORRECT**: Use proper interface or `unknown` with type guards
|
|
300
|
+
- ✅ **CORRECT**: `const result: unknown = ...` then narrow with type guards
|
|
301
|
+
- ✅ When types aren't available, use `unknown` and narrow:
|
|
302
|
+
```tsx
|
|
303
|
+
function handleResult(result: unknown) {
|
|
304
|
+
if (result && typeof result === 'object' && 'public_id' in result) {
|
|
305
|
+
// TypeScript knows result has public_id
|
|
306
|
+
const uploadResult = result as CloudinaryUploadResult;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Best Practices
|
|
312
|
+
- ✅ Always use `fill()` resize for responsive images
|
|
313
|
+
- ✅ Always end transformations with `.delivery(format(auto())).delivery(quality(autoQuality()))`
|
|
314
|
+
- ✅ Use `placeholder()` and `lazyload()` plugins together
|
|
315
|
+
- ✅ Always add `width` and `height` attributes to `AdvancedImage`
|
|
316
|
+
- ✅ Store `public_id` from upload success, not full URL
|
|
317
|
+
- ✅ Always dispose video player in useEffect cleanup
|
|
318
|
+
- ✅ Use TypeScript for better autocomplete and error catching
|
|
319
|
+
- ✅ Prefer `unknown` over `any` when types aren't available
|
|
320
|
+
- ✅ Use type guards for runtime type checking
|
|
321
|
+
- ✅ Define interfaces for Cloudinary API responses
|
|
322
|
+
- ✅ Create `vite-env.d.ts` for environment variable typing
|
|
323
|
+
- ✅ Use proper HTML element types for refs
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
# ⚠️ COMMON ERRORS & SOLUTIONS
|
|
328
|
+
|
|
329
|
+
## Environment Variable Errors
|
|
330
|
+
|
|
331
|
+
### "Cloud name is required"
|
|
332
|
+
- ❌ Problem: `VITE_CLOUDINARY_CLOUD_NAME` not set or wrong prefix
|
|
333
|
+
- ✅ Solution:
|
|
334
|
+
1. Check `.env` file exists in project root
|
|
335
|
+
2. Verify variable is `VITE_CLOUDINARY_CLOUD_NAME` (with `VITE_` prefix!)
|
|
336
|
+
3. Restart dev server after adding .env variables
|
|
337
|
+
|
|
338
|
+
### "VITE_ prefix required" or env var is undefined
|
|
339
|
+
- ❌ Problem: Variable doesn't have `VITE_` prefix
|
|
340
|
+
- ✅ Solution:
|
|
341
|
+
1. Rename `CLOUDINARY_CLOUD_NAME` → `VITE_CLOUDINARY_CLOUD_NAME`
|
|
342
|
+
2. Restart dev server
|
|
343
|
+
3. Use `import.meta.env.VITE_CLOUDINARY_CLOUD_NAME` (not `process.env`)
|
|
344
|
+
|
|
345
|
+
## Import Errors
|
|
346
|
+
|
|
347
|
+
### "Cannot find module" or wrong import
|
|
348
|
+
- ❌ Problem: Importing from wrong package
|
|
349
|
+
- ✅ Solution:
|
|
350
|
+
- Components: `@cloudinary/react` (not `@cloudinary/url-gen`)
|
|
351
|
+
- Transformations: `@cloudinary/url-gen/actions/*` (not `@cloudinary/react`)
|
|
352
|
+
- Cloudinary class: `@cloudinary/url-gen` (not `@cloudinary/react`)
|
|
353
|
+
|
|
354
|
+
## Transformation Errors
|
|
355
|
+
|
|
356
|
+
### "Transformation not working" or image looks wrong
|
|
357
|
+
- ❌ Problem: Incorrect transformation syntax
|
|
358
|
+
- ✅ Solution:
|
|
359
|
+
1. Check transformation is chained: `cld.image('id').resize(...).effect(...)`
|
|
360
|
+
2. Verify actions are imported from correct modules
|
|
361
|
+
3. Ensure image public_id is correct and accessible
|
|
362
|
+
4. Check transformation syntax matches v2 (not v1)
|
|
363
|
+
5. Format/quality must be separate: `.delivery(format(auto())).delivery(quality(autoQuality()))`
|
|
364
|
+
|
|
365
|
+
### Wrong transformation syntax
|
|
366
|
+
- ❌ WRONG: `<AdvancedImage src="image.jpg" width={800} />`
|
|
367
|
+
- ✅ CORRECT:
|
|
368
|
+
```tsx
|
|
369
|
+
const img = cld.image('image.jpg').resize(fill().width(800));
|
|
370
|
+
<AdvancedImage cldImg={img} />
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Plugin Errors
|
|
374
|
+
|
|
375
|
+
### "Responsive images not working" or "Placeholder issues"
|
|
376
|
+
- ❌ Problem: Plugins not configured correctly
|
|
377
|
+
- ✅ Solution:
|
|
378
|
+
1. Must use `responsive()` plugin with `fill()` resize
|
|
379
|
+
2. Include both `placeholder()` and `lazyload()` plugins
|
|
380
|
+
3. Check image is accessible and public_id is correct
|
|
381
|
+
4. Verify plugins are in array: `plugins={[responsive(), placeholder(), lazyload()]}`
|
|
382
|
+
5. Always add `width` and `height` attributes
|
|
383
|
+
|
|
384
|
+
### Plugins not working
|
|
385
|
+
- ❌ WRONG: `<AdvancedImage cldImg={img} lazyLoad placeholder />`
|
|
386
|
+
- ✅ CORRECT: `<AdvancedImage cldImg={img} plugins={[lazyload(), placeholder()]} />`
|
|
387
|
+
- ✅ Plugins must be imported from `@cloudinary/react`, not `@cloudinary/url-gen`
|
|
388
|
+
|
|
389
|
+
## Upload Widget Errors
|
|
390
|
+
|
|
391
|
+
### "Upload preset not found" or "Invalid upload preset"
|
|
392
|
+
- ❌ Problem: Preset doesn't exist or is signed
|
|
393
|
+
- ✅ Solution:
|
|
394
|
+
1. Create unsigned upload preset in Cloudinary dashboard
|
|
395
|
+
2. Go to `settings/upload/presets` > Add upload preset
|
|
396
|
+
3. Set to "Unsigned" mode
|
|
397
|
+
4. Copy exact preset name to `.env` as `VITE_CLOUDINARY_UPLOAD_PRESET`
|
|
398
|
+
5. Restart dev server
|
|
399
|
+
|
|
400
|
+
### "Cannot upload large images" or "Upload fails for large files"
|
|
401
|
+
- ❌ Problem: File too large or script not loaded
|
|
402
|
+
- ✅ Solution:
|
|
403
|
+
1. Use chunked uploads for files > 20MB
|
|
404
|
+
2. Check upload preset has appropriate limits in dashboard
|
|
405
|
+
3. Verify `window.cloudinary` script is loaded in `index.html`
|
|
406
|
+
4. Consider using server-side upload for very large files
|
|
407
|
+
|
|
408
|
+
### Widget not opening
|
|
409
|
+
- ❌ Problem: Script not loaded or initialization issue
|
|
410
|
+
- ✅ Solution:
|
|
411
|
+
1. Ensure script is in `index.html`: `<script src="https://upload-widget.cloudinary.com/global/all.js" async></script>`
|
|
412
|
+
2. Check widget initializes in `useEffect` after `window.cloudinary` is available
|
|
413
|
+
3. Verify upload preset is set correctly
|
|
414
|
+
|
|
415
|
+
## Video Errors
|
|
416
|
+
|
|
417
|
+
### "AdvancedVideo not working" or "Video not displaying"
|
|
418
|
+
- ❌ Problem: Wrong component or incorrect setup
|
|
419
|
+
- ✅ Solution:
|
|
420
|
+
1. Verify you're using `AdvancedVideo` from `@cloudinary/react` (not video player)
|
|
421
|
+
2. Check video instance is created: `const video = cld.video(publicId)`
|
|
422
|
+
3. **NO CSS IMPORT NEEDED** - AdvancedVideo doesn't require CSS import
|
|
423
|
+
4. ❌ **WRONG**: `import '@cloudinary/react/dist/cld-video-player.css'` (this path doesn't exist)
|
|
424
|
+
5. Verify public ID is correct and video exists in Cloudinary
|
|
425
|
+
6. Check transformations are chained correctly (same as images)
|
|
426
|
+
|
|
427
|
+
### "Failed to resolve import @cloudinary/react/dist/cld-video-player.css"
|
|
428
|
+
- ❌ Problem: Trying to import CSS that doesn't exist in `@cloudinary/react`
|
|
429
|
+
- ✅ Solution:
|
|
430
|
+
1. **Remove the CSS import** - AdvancedVideo doesn't need it
|
|
431
|
+
2. The `cld-video-player.css` file is only for `cloudinary-video-player` package
|
|
432
|
+
3. AdvancedVideo uses native HTML5 video elements - no CSS required
|
|
433
|
+
4. If you need styled video player, use `cloudinary-video-player` instead
|
|
434
|
+
|
|
435
|
+
### "Video player not working" or "Player not initializing"
|
|
436
|
+
- ❌ Problem: Configuration or initialization issue with standalone player
|
|
437
|
+
- ✅ Solution:
|
|
438
|
+
1. Check `cloud_name` is provided in player config
|
|
439
|
+
2. Ensure CSS file is imported: `import 'cloudinary-video-player/dist/cld-video-player.css'`
|
|
440
|
+
3. Verify player is initialized in `useEffect` with proper cleanup
|
|
441
|
+
4. Check video element has required classes: `cld-video-player cld-fluid`
|
|
442
|
+
5. Always call `player.dispose()` in useEffect cleanup function
|
|
443
|
+
6. For advanced features, ensure required modules are imported
|
|
444
|
+
|
|
445
|
+
### Confusion between AdvancedVideo and Video Player
|
|
446
|
+
- ❌ WRONG: Using `cloudinary-video-player` when you just need transformations
|
|
447
|
+
- ✅ CORRECT: Use `AdvancedVideo` from `@cloudinary/react` for simple video playback with transformations
|
|
448
|
+
- ❌ WRONG: Using `AdvancedVideo` when you need playlists/recommendations/ads
|
|
449
|
+
- ✅ CORRECT: Use `cloudinary-video-player` for advanced features like playlists, recommendations, ads
|
|
450
|
+
|
|
451
|
+
### Memory leak from video player
|
|
452
|
+
- ❌ WRONG: Not disposing player in cleanup
|
|
453
|
+
- ✅ CORRECT: Always dispose player:
|
|
454
|
+
```tsx
|
|
455
|
+
useEffect(() => {
|
|
456
|
+
const player = cloudinary.videoPlayer(ref.current, config);
|
|
457
|
+
return () => player.dispose(); // Always cleanup!
|
|
458
|
+
}, [dependencies]);
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## TypeScript Errors
|
|
462
|
+
|
|
463
|
+
### "TypeScript errors on transformations"
|
|
464
|
+
- ❌ Problem: Missing types or wrong imports
|
|
465
|
+
- ✅ Solution:
|
|
466
|
+
1. Import types from `@cloudinary/url-gen`
|
|
467
|
+
2. Use proper action imports: `import { fill } from '@cloudinary/url-gen/actions/resize'`
|
|
468
|
+
3. Type the image instance if needed: `const img: CloudinaryImage = cld.image('id')`
|
|
469
|
+
4. Ensure all imports are from correct modules
|
|
470
|
+
|
|
471
|
+
### "Type 'any' is not assignable" or "Parameter 'result' implicitly has 'any' type"
|
|
472
|
+
- ❌ Problem: Using `any` type or missing type definitions
|
|
473
|
+
- ✅ Solution:
|
|
474
|
+
1. Define interface for upload results: `interface CloudinaryUploadResult { ... }`
|
|
475
|
+
2. Type callbacks: `onUploadSuccess?: (result: CloudinaryUploadResult) => void`
|
|
476
|
+
3. Use `unknown` instead of `any` when types aren't available
|
|
477
|
+
4. Add type guards to narrow `unknown` types
|
|
478
|
+
|
|
479
|
+
### "Property 'cloudinary' does not exist on type 'Window'"
|
|
480
|
+
- ❌ Problem: Missing type declaration for window.cloudinary
|
|
481
|
+
- ✅ Solution:
|
|
482
|
+
```tsx
|
|
483
|
+
declare global {
|
|
484
|
+
interface Window {
|
|
485
|
+
cloudinary?: {
|
|
486
|
+
createUploadWidget: (config: any, callback: any) => any;
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
- ✅ Or use type guard: `if (typeof window.cloudinary !== 'undefined')`
|
|
492
|
+
|
|
493
|
+
### "Property 'VITE_CLOUDINARY_CLOUD_NAME' does not exist on type 'ImportMetaEnv'"
|
|
494
|
+
- ❌ Problem: Missing type definitions for Vite environment variables
|
|
495
|
+
- ✅ Solution:
|
|
496
|
+
1. Create `vite-env.d.ts` file
|
|
497
|
+
2. Add interface: `interface ImportMetaEnv { readonly VITE_CLOUDINARY_CLOUD_NAME: string; }`
|
|
498
|
+
3. Reference Vite types: `/// <reference types="vite/client" />`
|
|
499
|
+
|
|
500
|
+
### "Type 'null' is not assignable to type 'RefObject'"
|
|
501
|
+
- ❌ Problem: Incorrect ref typing
|
|
502
|
+
- ✅ Solution:
|
|
503
|
+
1. Use proper HTML element type: `useRef<HTMLVideoElement>(null)`
|
|
504
|
+
2. Use `unknown` for widget refs if types aren't available: `useRef<unknown>(null)`
|
|
505
|
+
3. Check for null before accessing: `if (ref.current) { ... }`
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Quick Reference Checklist
|
|
510
|
+
|
|
511
|
+
When something isn't working, check:
|
|
512
|
+
- [ ] Environment variables have `VITE_` prefix
|
|
513
|
+
- [ ] Dev server was restarted after .env changes
|
|
514
|
+
- [ ] Imports are from correct packages
|
|
515
|
+
- [ ] Transformations are chained on image instance
|
|
516
|
+
- [ ] Format/quality use separate `.delivery()` calls
|
|
517
|
+
- [ ] Plugins are in array format
|
|
518
|
+
- [ ] Upload widget script is loaded in `index.html`
|
|
519
|
+
- [ ] Upload preset is unsigned
|
|
520
|
+
- [ ] Video player is disposed in cleanup
|
|
521
|
+
- [ ] CSS files are imported for video player
|
|
522
|
+
- [ ] TypeScript types are properly imported
|
|
523
|
+
- [ ] Upload result types are defined (not using `any`)
|
|
524
|
+
- [ ] Environment variables are typed in `vite-env.d.ts`
|
|
525
|
+
- [ ] Refs are properly typed with HTML element types
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Cloudinary Configuration
|
|
2
|
+
# IMPORTANT: In Vite, environment variables must be prefixed with VITE_ to be exposed to the browser
|
|
3
|
+
# Copy this file to .env and fill in your values
|
|
4
|
+
|
|
5
|
+
VITE_CLOUDINARY_CLOUD_NAME=your_cloud_name
|
|
6
|
+
VITE_CLOUDINARY_UPLOAD_PRESET=your_upload_preset
|
|
7
|
+
|
|
8
|
+
# For unsigned uploads, create an upload preset in your Cloudinary dashboard
|
|
9
|
+
# Settings > Upload > Upload presets > Add upload preset
|
|
10
|
+
# Make it "Unsigned" to allow client-side uploads
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
pnpm-debug.log*
|
|
8
|
+
lerna-debug.log*
|
|
9
|
+
|
|
10
|
+
node_modules
|
|
11
|
+
dist
|
|
12
|
+
dist-ssr
|
|
13
|
+
*.local
|
|
14
|
+
|
|
15
|
+
# Environment variables
|
|
16
|
+
.env
|
|
17
|
+
.env.local
|
|
18
|
+
.env.production
|
|
19
|
+
|
|
20
|
+
# Editor directories and files
|
|
21
|
+
.vscode/*
|
|
22
|
+
!.vscode/extensions.json
|
|
23
|
+
.idea
|
|
24
|
+
.DS_Store
|
|
25
|
+
*.suo
|
|
26
|
+
*.ntvs*
|
|
27
|
+
*.njsproj
|
|
28
|
+
*.sln
|
|
29
|
+
*.sw?
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A Cloudinary React + Vite + TypeScript project scaffolded with [create-cloudinary-react](https://github.com/cloudinary-devs/create-cloudinary-react).
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Cloudinary Setup
|
|
12
|
+
|
|
13
|
+
This project uses Cloudinary for image management. If you don't have a Cloudinary account yet:
|
|
14
|
+
- [Sign up for free](https://cloudinary.com/users/register/free)
|
|
15
|
+
- Find your cloud name in your [dashboard](https://console.cloudinary.com/app/home/dashboard)
|
|
16
|
+
|
|
17
|
+
## Environment Variables
|
|
18
|
+
|
|
19
|
+
Your `.env` file has been pre-configured with:
|
|
20
|
+
- `VITE_CLOUDINARY_CLOUD_NAME`: {{CLOUD_NAME}}
|
|
21
|
+
{{#UPLOAD_PRESET}}
|
|
22
|
+
- `VITE_CLOUDINARY_UPLOAD_PRESET`: {{UPLOAD_PRESET}}
|
|
23
|
+
{{/UPLOAD_PRESET}}
|
|
24
|
+
{{^UPLOAD_PRESET}}
|
|
25
|
+
- `VITE_CLOUDINARY_UPLOAD_PRESET`: (not set - required for uploads)
|
|
26
|
+
|
|
27
|
+
**Note**: Transformations work without an upload preset (using sample images). Uploads require an unsigned upload preset.
|
|
28
|
+
|
|
29
|
+
To create an upload preset:
|
|
30
|
+
1. Go to https://console.cloudinary.com/app/settings/upload/presets
|
|
31
|
+
2. Click "Add upload preset"
|
|
32
|
+
3. Set it to "Unsigned" mode
|
|
33
|
+
4. Add the preset name to your `.env` file
|
|
34
|
+
{{/UPLOAD_PRESET}}
|
|
35
|
+
|
|
36
|
+
## AI Assistant Support
|
|
37
|
+
|
|
38
|
+
This project includes AI coding rules for your selected AI assistant(s). The rules help AI assistants understand Cloudinary React SDK patterns, common errors, and best practices.
|
|
39
|
+
|
|
40
|
+
**Try the AI Prompts**: Check out the "🤖 Try Asking Your AI Assistant" section in the app for ready-to-use Cloudinary prompts to get started!
|
|
41
|
+
|
|
42
|
+
## Learn More
|
|
43
|
+
|
|
44
|
+
- [Cloudinary React SDK Docs](https://cloudinary.com/documentation/react_integration)
|
|
45
|
+
- [Vite Documentation](https://vite.dev)
|
|
46
|
+
- [React Documentation](https://react.dev)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
globalIgnores(['dist']),
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
extends: [
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
reactHooks.configs.flat.recommended,
|
|
16
|
+
reactRefresh.configs.vite,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
ecmaVersion: 2020,
|
|
20
|
+
globals: globals.browser,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>{{PROJECT_NAME}}</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@cloudinary/react": "^1.14.3",
|
|
14
|
+
"@cloudinary/url-gen": "^1.22.0",
|
|
15
|
+
"react": "^19.2.0",
|
|
16
|
+
"react-dom": "^19.2.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@eslint/js": "^9.39.1",
|
|
20
|
+
"@types/node": "^24.10.1",
|
|
21
|
+
"@types/react": "^19.2.5",
|
|
22
|
+
"@types/react-dom": "^19.2.3",
|
|
23
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
24
|
+
"eslint": "^9.39.1",
|
|
25
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
26
|
+
"eslint-plugin-react-refresh": "^0.4.24",
|
|
27
|
+
"globals": "^16.5.0",
|
|
28
|
+
"typescript": "~5.9.3",
|
|
29
|
+
"typescript-eslint": "^8.46.4",
|
|
30
|
+
"vite": "^6.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|