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.
@@ -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,5 @@
1
+ # Cloudinary Configuration
2
+ # IMPORTANT: In Vite, environment variables must be prefixed with VITE_ to be exposed to the browser
3
+
4
+ VITE_CLOUDINARY_CLOUD_NAME={{CLOUD_NAME}}
5
+ VITE_CLOUDINARY_UPLOAD_PRESET={{UPLOAD_PRESET}}
@@ -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
+ }