cloudinary-react

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloudinary React Skill

Cloudinary React 技能

When to Use

使用场景

  • When a user is building or debugging Cloudinary in a React app (Vite, Create React App, Parcel, etc.).
  • When implementing or fixing: Upload Widget, AdvancedImage/AdvancedVideo, transformations, overlays, image galleries, video player, or signed/unsigned uploads.
  • When the user sees errors like "createUploadWidget is not a function", wrong imports from
    @cloudinary/url-gen
    , upload preset issues, or video player DOM errors.
  • 当用户在React应用(Vite、Create React App、Parcel等)中集成或调试Cloudinary时。
  • 当需要实现或修复以下功能时:上传组件、AdvancedImage/AdvancedVideo、媒体转换、叠加层、图片画廊、视频播放器、签名/未签名上传。
  • 当用户遇到如下错误时:"createUploadWidget is not a function"、
    @cloudinary/url-gen
    导入错误、上传预设问题、视频播放器DOM错误。

Quick Start

快速开始

Most common operations:
  1. Setup: Create config file with
    cld
    instance (see Project setup section)
  2. Display image:
    const img = cld.image('id').resize(fill().width(800)); <AdvancedImage cldImg={img} />
  3. Upload Widget: Script in index.html + poll for
    createUploadWidget
    in useEffect
  4. Image overlay: Use
    source(text(...))
    or
    source(image(...))
    - see Import reference table for exact paths
  5. Signed uploads: See references/signed-uploads.md
  6. Troubleshooting: See references/troubleshooting.md
For TypeScript: See references/typescript-patterns.md For Video Player: See references/video-player.md
最常用操作:
  1. 初始化配置:创建包含
    cld
    实例的配置文件(见项目初始化章节)
  2. 展示图片
    const img = cld.image('id').resize(fill().width(800)); <AdvancedImage cldImg={img} />
  3. 上传组件:在index.html中引入脚本 + 在useEffect中轮询等待
    createUploadWidget
    加载完成
  4. 图片叠加层:使用
    source(text(...))
    source(image(...))
    —— 参考导入对照表获取准确导入路径
  5. 签名上传:查看 references/signed-uploads.md
  6. 问题排查:查看 references/troubleshooting.md
TypeScript相关:查看 references/typescript-patterns.md 视频播放器相关:查看 references/video-player.md

Instructions

使用说明

When helping with Cloudinary in React, follow the patterns and rules below. Use the exact import paths and code patterns specified; do not guess subpaths or invent APIs.

当协助解决React中Cloudinary相关问题时,请遵循以下模式和规则。严格使用指定的导入路径和代码模式,不要猜测子路径或自创API。

Cloudinary React SDK Patterns & Common Errors

Cloudinary React SDK 模式与常见错误

Scope: These rules apply to React (web) with the browser Upload Widget. The default is Vite (create-cloudinary-react uses Vite). They also work with other bundlers (Create React App, Next.js, Parcel, etc.): only how you read env vars changes; see "Other bundlers (non-Vite)" below. Rules-only users: see "Project setup (rules-only / without CLI)" for the reusable Cloudinary instance, env, Upload Widget (unsigned/signed), and video player. For React Native uploads (including signed upload), see: https://cloudinary.com/documentation/react_native_image_and_video_upload#signed_upload — same "never expose secret, generate signature on backend" principle, but React Native uses the
upload()
method and backend SDKs differently.
适用范围:这些规则适用于React(网页端)搭配浏览器端上传组件的场景,默认适配Vite(create-cloudinary-react默认使用Vite),也适用于其他打包工具(Create React App、Next.js、Parcel等):仅环境变量的读取方式有差异,见下文**"其他打包工具(非Vite)"章节。仅使用规则的用户:查看"项目初始化(仅用规则/不使用CLI)"获取可复用的Cloudinary实例、环境变量配置、上传组件(签名/未签名)和视频播放器配置。如果是React Native**上传(包含签名上传),请查看:https://cloudinary.com/documentation/react_native_image_and_video_upload#signed_upload —— 遵循相同的"永远不要暴露密钥,在后端生成签名"原则,但React Native使用的
upload()
方法和后端SDK的用法有差异。

Official Documentation

官方文档

For complete API references and all configuration options, consult the Cloudinary documentation via llms.txt: https://cloudinary.com/documentation/llms.txt
This skill provides React patterns and common errors. For detailed API options (e.g., all Upload Widget config properties, Video Player API methods), use the documentation markdown files available through llms.txt.
如需完整的API参考和所有配置选项,请通过llms.txt查阅Cloudinary官方文档:https://cloudinary.com/documentation/llms.txt
本技能提供React相关的使用模式和常见错误解决方案。如需详细的API选项(例如上传组件的所有配置属性、视频播放器的API方法),请使用llms.txt提供的markdown格式文档。

Key References

核心参考

Golden rule for @cloudinary/url-gen: When generating overlay or transformation code, use only the "Import reference" table and "Canonical overlay block" in these rules. Copy those import paths and usage patterns exactly; do not guess or derive paths (e.g.
text
and
image
are from
qualifiers/source
, not
actions/overlay
;
Position
is from
qualifiers/position
, not
positioning/Position
).

@cloudinary/url-gen黄金规则:生成叠加层或转换代码时,仅使用本规则中的「导入对照表」和「标准叠加层代码块」,严格复制导入路径和使用模式,不要猜测或推导路径(例如
text
image
qualifiers/source
导入,而非
actions/overlay
Position
qualifiers/position
导入,而非
positioning/Position
)。

📋 PATTERNS (How to Do It Right)

📋 最佳实践模式(正确实现方式)

Project setup (rules-only / without CLI)

项目初始化(仅用规则/不使用CLI)

If the user is not using the create-cloudinary-react CLI and only has these rules, generate the following so they get correct config, env, and widget setup.
1. Environment (.env)
Create a
.env
file in the project root with Vite prefix (required for client access):
  • VITE_CLOUDINARY_CLOUD_NAME_=my_cloud
    (required — use your actual cloud name, never the literal string
    your_cloud_name
    which causes 401)
  • VITE_CLOUDINARY_UPLOAD_PRESET=my_preset
    (optional; required for unsigned upload widget — use your actual preset name)
  • Restart the dev server after adding or changing
    .env
    . Use
    import.meta.env.VITE_*
    in code, not
    process.env
    .
  • If env var still empty in browser after restart: Vite may cache the old value. Clear
    node_modules/.vite/
    , restart dev server, and do a hard refresh (Cmd+Shift+R / Ctrl+Shift+F5). If still empty, see "Vite env not reaching client" in Common Errors.
2. Reusable Cloudinary instance (config)
Create a config file (e.g.
src/cloudinary/config.ts
) so the rest of the app can use a single
cld
instance:
ts
import { Cloudinary } from '@cloudinary/url-gen';

const cloudName = import.meta.env.VITE_CLOUDINARY_CLOUD_NAME;
if (!cloudName) {
  throw new Error('VITE_CLOUDINARY_CLOUD_NAME is not set. Add it to .env with the VITE_ prefix.');
}

export const cld = new Cloudinary({ cloud: { cloudName } });
export const uploadPreset = import.meta.env.VITE_CLOUDINARY_UPLOAD_PRESET || '';
  • Use this pattern for the reusable instance. Everywhere else:
    import { cld } from './cloudinary/config'
    (or the path the user chose) and call
    cld.image(publicId)
    /
    cld.video(publicId)
    .
3. Upload Widget (unsigned, from scratch)
Strict pattern (always follow this exactly):
  1. Script in
    index.html
    (required): Add
    <script src="https://upload-widget.cloudinary.com/global/all.js" async></script>
    to
    index.html
    . Do not rely only on dynamic script injection from React — it's fragile.
  2. Poll in useEffect (required): In
    useEffect
    , poll with
    setInterval
    (e.g. every 100ms) until
    typeof window.cloudinary?.createUploadWidget === 'function'
    . Only then create the widget. A single check (even in
    onload
    ) is not reliable because
    window.cloudinary
    can exist before
    createUploadWidget
    is attached.
  3. Add a timeout: Set a timeout (e.g. 10 seconds) to stop polling and show an error if the script never loads. Clear both interval and timeout in cleanup.
  4. Create widget once: When
    createUploadWidget
    is available, create the widget and store it in a ref. Clear the interval and timeout. Pass options:
    { cloudName, uploadPreset, sources: ['local', 'camera', 'url'], multiple: false }
    .
  5. Open on click: Attach a click listener to a button that calls
    widgetRef.current?.open()
    . Remove the listener in useEffect cleanup.
Do NOT: Check only
window.cloudinary
(not enough); do a single check in
onload
(unreliable); skip the script in
index.html
; poll forever without a timeout.
  • Signed uploads: Do not use only
    uploadPreset
    ; use the pattern under "Secure (Signed) Uploads" (uploadSignature as function, fetch api_key, server includes upload_preset in signature).
4. Video player
  • Use imperative video element only (create with document.createElement, append to container ref, pass to videoPlayer). See "Cloudinary Video Player (The Player)" for the full pattern.
5. Summary for rules-only users
  • Env: Use your bundler's client env prefix and access (Vite:
    VITE_
    +
    import.meta.env.VITE_*
    ; see "Other bundlers" if not Vite).
  • Reusable instance: One config file that creates and exports
    cld
    (and optionally
    uploadPreset
    ) from
    @cloudinary/url-gen
    ; use it everywhere.
  • Upload widget: Script in index.html (required); in useEffect, poll until
    createUploadWidget
    is a function, then create widget once and store in ref; unsigned = cloudName + uploadPreset; signed = use uploadSignature function and backend.
  • Video player: Imperative video element (createElement, append to container ref, pass to videoPlayer); dispose + removeChild in cleanup; fall back to AdvancedVideo if init fails.
If the user is not using Vite: Use their bundler's client env prefix and access in the config file and everywhere you read env. Examples: Create React App →
REACT_APP_CLOUDINARY_CLOUD_NAME
,
process.env.REACT_APP_CLOUDINARY_CLOUD_NAME
; Next.js (client) →
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME
,
process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME
. The rest (cld instance, widget options, video player) is the same.
如果用户不使用create-cloudinary-react CLI,仅遵循本规则,请生成以下配置,确保他们得到正确的配置、环境变量和组件初始化方式。
1. 环境变量(.env)
在项目根目录创建
.env
文件,使用Vite前缀(客户端访问必需):
  • VITE_CLOUDINARY_CLOUD_NAME_=my_cloud
    (必填 —— 替换为你实际的云名称,永远不要使用字面量
    your_cloud_name
    ,会导致401错误)
  • VITE_CLOUDINARY_UPLOAD_PRESET=my_preset
    (可选,未签名上传组件必填 —— 替换为你实际的预设名称)
  • 添加或修改
    .env
    重启开发服务。代码中使用
    import.meta.env.VITE_*
    读取变量,不要用
    process.env
  • 如果重启后浏览器中环境变量仍然为空:Vite可能缓存了旧值,删除
    node_modules/.vite/
    目录,重启开发服务,然后强制刷新页面(Cmd+Shift+R / Ctrl+Shift+F5)。如果仍然为空,查看常见错误中的「Vite环境变量未传递到客户端」章节。
2. 可复用Cloudinary实例(配置)
创建配置文件(例如
src/cloudinary/config.ts
),让应用其他部分可以复用同一个
cld
实例:
ts
import { Cloudinary } from '@cloudinary/url-gen';

const cloudName = import.meta.env.VITE_CLOUDINARY_CLOUD_NAME;
if (!cloudName) {
  throw new Error('VITE_CLOUDINARY_CLOUD_NAME is not set. Add it to .env with the VITE_ prefix.');
}

export const cld = new Cloudinary({ cloud: { cloudName } });
export const uploadPreset = import.meta.env.VITE_CLOUDINARY_UPLOAD_PRESET || '';
  • 请使用此模式创建可复用实例,其他地方直接导入:
    import { cld } from './cloudinary/config'
    (或用户自定义的路径),然后调用
    cld.image(publicId)
    /
    cld.video(publicId)
    即可。
3. 上传组件(未签名版,从零实现)
严格遵循以下模式:
  1. index.html
    中引入脚本
    (必填):添加
    <script src="https://upload-widget.cloudinary.com/global/all.js" async></script>
    index.html
    中。不要仅依赖React动态注入脚本,这种方式不稳定。
  2. 在useEffect中轮询检测(必填):在
    useEffect
    中使用
    setInterval
    (例如每100ms检测一次),直到
    typeof window.cloudinary?.createUploadWidget === 'function
    时再创建组件。仅做单次检测(即使是在
    onload
    中)不可靠,因为
    window.cloudinary
    可能在
    createUploadWidget
    挂载前就已经存在。
  3. 添加超时逻辑:设置超时时间(例如10秒),如果脚本始终未加载完成则停止轮询并展示错误。在清理函数中同时清除定时器和超时器。
  4. 仅创建一次组件:当
    createUploadWidget
    可用时,创建组件并存入ref中,清除定时器和超时器。传入选项:
    { cloudName, uploadPreset, sources: ['local', 'camera', 'url'], multiple: false }
  5. 点击触发打开:给按钮绑定点击事件,调用
    widgetRef.current?.open()
    。在useEffect清理函数中移除事件监听。
禁止操作:仅检测
window.cloudinary
(不充分);仅在
onload
中做单次检测(不可靠);不在
index.html
中引入脚本;无超时无限轮询。
  • 签名上传:不要仅使用
    uploadPreset
    ,请使用「安全(签名)上传」章节的模式(uploadSignature作为函数,后端返回api_key,签名中包含upload_preset)。
4. 视频播放器
  • 仅使用命令式创建video元素(用document.createElement创建,追加到容器ref中,再传入videoPlayer)。完整模式见「Cloudinary视频播放器」章节。
5. 仅用规则的用户总结
  • 环境变量:使用你所用打包工具的客户端环境变量前缀和读取方式(Vite:
    VITE_
    前缀 +
    import.meta.env.VITE_*
    ;非Vite查看「其他打包工具」章节)。
  • 可复用实例:在一个配置文件中从
    @cloudinary/url-gen
    创建并导出
    cld
    (可选导出
    uploadPreset
    ),全局复用。
  • 上传组件:必须在index.html中引入脚本;在useEffect中轮询直到
    createUploadWidget
    是函数,然后创建一次组件存入ref;未签名版 = cloudName + uploadPreset;签名版 = 使用uploadSignature函数 + 后端支持。
  • 视频播放器:命令式创建video元素(createElement,追加到容器ref,传入videoPlayer);清理时dispose + removeChild;如果初始化失败回退到AdvancedVideo。
如果用户不使用Vite:在配置文件和所有读取环境变量的地方使用对应打包工具的客户端前缀和读取方式。示例:Create React App →
REACT_APP_CLOUDINARY_CLOUD_NAME
process.env.REACT_APP_CLOUDINARY_CLOUD_NAME
;Next.js(客户端)→
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME
process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME
。其他部分(cld实例、组件选项、视频播放器)完全一致。

Environment Variables

环境变量

  • Default: Vite — Vite requires
    VITE_
    prefix; use
    import.meta.env.VITE_CLOUDINARY_CLOUD_NAME
    (not
    process.env
    ). Restart dev server after changing
    .env
    .
  • ✅ CORRECT (Vite):
    VITE_CLOUDINARY_CLOUD_NAME=mycloud
    in
    .env
    ;
    import.meta.env.VITE_CLOUDINARY_CLOUD_NAME
  • WRONG:
    VITE_CLOUDINARY_CLOUD_NAME=your_cloud_name
    Never use the literal placeholder
    your_cloud_name
    ; it causes 401 errors. Use your actual cloud name from the Cloudinary dashboard.
  • ⚠️ Vite env not reaching client: If
    import.meta.env.VITE_CLOUDINARY_CLOUD_NAME
    is still
    undefined
    in the browser after restarting:
    1. Clear Vite cache:
      rm -rf node_modules/.vite/
      (or delete
      node_modules/.vite
      folder)
    2. Restart dev server:
      npm run dev
    3. Hard refresh browser: Cmd+Shift+R (Mac) or Ctrl+Shift+F5 (Windows/Linux)
    4. If still empty, use a static config workaround (create a
      cloudinaryConfig.ts
      with a hardcoded cloud name for dev, then switch back to env later) and see Common Errors → "VITE_ prefix required or env var is undefined"
  • 默认:Vite —— Vite要求环境变量带
    VITE_
    前缀,使用
    import.meta.env.VITE_CLOUDINARY_CLOUD_NAME
    读取(不要用
    process.env
    )。修改
    .env
    后重启开发服务。
  • ✅ 正确用法(Vite):
    .env
    中写
    VITE_CLOUDINARY_CLOUD_NAME=mycloud
    ;代码中用
    import.meta.env.VITE_CLOUDINARY_CLOUD_NAME
    读取
  • 错误用法
    VITE_CLOUDINARY_CLOUD_NAME=your_cloud_name
    —— 永远不要使用字面量占位符
    your_cloud_name
    ,会导致401错误,请替换为Cloudinary控制台中实际的云名称。
  • ⚠️ Vite环境变量未传递到客户端:如果重启后浏览器中
    import.meta.env.VITE_CLOUDINARY_CLOUD_NAME
    仍然是
    undefined
    1. 清除Vite缓存:
      rm -rf node_modules/.vite/
      (或手动删除
      node_modules/.vite
      文件夹)
    2. 重启开发服务:
      npm run dev
    3. 强制刷新浏览器:Mac按Cmd+Shift+R,Windows/Linux按Ctrl+Shift+F5
    4. 如果仍然为空,使用静态配置临时解决(创建
      cloudinaryConfig.ts
      硬编码云名称用于开发,后续再切回环境变量),并查看常见错误 → 「需要VITE_前缀或环境变量未定义」章节

Other bundlers (non-Vite)

其他打包工具(非Vite)

  • Only the env access changes. All other patterns (reusable
    cld
    , Upload Widget, Video Player, overlays, signed uploads) are bundler-agnostic.
  • Create React App: Prefix
    REACT_APP_
    ; access
    process.env.REACT_APP_CLOUDINARY_CLOUD_NAME
    ,
    process.env.REACT_APP_CLOUDINARY_UPLOAD_PRESET
    . Restart dev server after
    .env
    changes.
  • Next.js (client): Prefix
    NEXT_PUBLIC_
    for client; access
    process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME
    , etc. Server-side can use
    process.env.CLOUDINARY_*
    without
    NEXT_PUBLIC_
    .
  • Parcel / other: Check the bundler's docs for "exposing environment variables to the client" (often a prefix or allowlist). Use that prefix and the documented access (e.g.
    process.env.*
    ).
  • Config file: In
    src/cloudinary/config.ts
    (or equivalent), read cloud name and upload preset using the user's bundler env API (e.g. for CRA:
    process.env.REACT_APP_CLOUDINARY_CLOUD_NAME
    ). Same
    new Cloudinary({ cloud: { cloudName } })
    and exports; only the env read line changes.
  • 仅环境变量的读取方式有变化,其他所有模式(可复用
    cld
    实例、上传组件、视频播放器、叠加层、签名上传)都和打包工具无关。
  • Create React App:前缀为
    REACT_APP_
    ;使用
    process.env.REACT_APP_CLOUDINARY_CLOUD_NAME
    process.env.REACT_APP_CLOUDINARY_UPLOAD_PRESET
    读取。修改
    .env
    后重启开发服务。
  • Next.js(客户端):客户端使用的环境变量前缀为
    NEXT_PUBLIC_
    ;使用
    process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME
    等读取。服务端可以直接使用
    process.env.CLOUDINARY_*
    ,不需要
    NEXT_PUBLIC_
    前缀。
  • Parcel / 其他打包工具:查看打包工具文档中「向客户端暴露环境变量」的说明(通常需要前缀或白名单),使用对应前缀和文档中的读取方式(例如
    process.env.*
    )。
  • 配置文件:在
    src/cloudinary/config.ts
    (或等效路径)中,使用用户所用打包工具的环境变量API读取云名称和上传预设(例如CRA用
    process.env.REACT_APP_CLOUDINARY_CLOUD_NAME
    )。
    new Cloudinary({ cloud: { cloudName } })
    和导出逻辑完全一致,仅读取环境变量的行有变化。

Upload Presets

上传预设

  • Unsigned = client-only uploads (no backend). Signed = backend required, more secure. See "Signed vs unsigned uploads" below for when to use which.
  • ✅ Create unsigned upload preset (for simple client uploads): https://console.cloudinary.com/app/settings/upload/presets
  • ✅ Set preset in
    .env
    :
    VITE_CLOUDINARY_UPLOAD_PRESET=your-preset-name
  • ✅ Use in code:
    import { uploadPreset } from './cloudinary/config'
  • ⚠️ If upload preset is missing, the Upload Widget will show an error message
  • ⚠️ Upload presets must be set to "Unsigned" mode for client-side usage (no API key/secret needed)
  • When unsigned upload fails: First check that the user configured their upload preset:
    1. Is
      VITE_CLOUDINARY_UPLOAD_PRESET
      set in
      .env
      ? (must match preset name exactly)
    2. Does the preset exist in the dashboard under Settings → Upload → Upload presets?
    3. Is the preset set to Unsigned (not Signed)?
    4. Was the dev server restarted after adding/updating
      .env
      ?
  • 未签名版 = 纯客户端上传(不需要后端)。签名版 = 需要后端支持,安全性更高。何时使用见下文「签名vs未签名上传」章节。
  • ✅ 创建未签名上传预设(用于简单的客户端上传):https://console.cloudinary.com/app/settings/upload/presets
  • ✅ 在
    .env
    中设置预设:
    VITE_CLOUDINARY_UPLOAD_PRESET=your-preset-name
  • ✅ 代码中使用:
    import { uploadPreset } from './cloudinary/config'
  • ⚠️ 如果缺失上传预设,上传组件会展示错误信息
  • ⚠️ 客户端使用的上传预设必须设置为「未签名」模式(不需要API密钥/secret)
  • 未签名上传失败时:首先检查用户是否正确配置了上传预设:
    1. .env
      中是否设置了
      VITE_CLOUDINARY_UPLOAD_PRESET
      ?(必须和预设名称完全匹配)
    2. 控制台的设置 → 上传 → 上传预设中是否存在该预设?
    3. 预设是否设置为未签名(不是签名)模式?
    4. 添加/更新
      .env
      后是否重启了开发服务?

Installing Cloudinary packages

安装Cloudinary依赖包

  • Install the latest: When adding Cloudinary packages, use
    npm install <package>
    with no version so npm installs the latest compatible version (e.g.
    npm install cloudinary-video-player
    ). In package.json use a caret range (e.g.
    "cloudinary-video-player": "^1.0.0"
    ) so future installs get the latest compatible. Do not pin to an exact version unless you have verified it exists on npm.
  • Package names only: Use only these names:
    @cloudinary/react
    ,
    @cloudinary/url-gen
    ,
    cloudinary-video-player
    (standalone player),
    cloudinary
    (Node server-side only). Do not invent names (e.g. no
    @cloudinary/video-player
    ).
  • WRONG:
    npm install cloudinary-video-player@1.2.3
    or
    "cloudinary-video-player": "1.2.3"
    (exact pin) — versions may not exist and break installs.
  • Correct:
    npm install cloudinary-video-player
    (no version) or in package.json:
    "cloudinary-video-player": "^1.0.0"
    (caret = latest compatible).
  • 安装最新版本:添加Cloudinary依赖时,使用
    npm install <package>
    不带版本号,让npm安装最新的兼容版本(例如
    npm install cloudinary-video-player
    )。package.json中使用插入号范围(例如
    "cloudinary-video-player": "^1.0.0"
    ),这样后续安装会获取最新兼容版本。除非你确认某个版本确实在npm上存在,否则不要固定到精确版本。
  • 仅使用官方包名:仅使用以下包名:
    @cloudinary/react
    @cloudinary/url-gen
    cloudinary-video-player
    (独立播放器)、
    cloudinary
    (仅Node服务端使用)。不要自创包名(例如不要用
    @cloudinary/video-player
    )。
  • 错误用法
    npm install cloudinary-video-player@1.2.3
    "cloudinary-video-player": "1.2.3"
    (固定精确版本)—— 版本可能不存在,导致安装失败。
  • 正确用法
    npm install cloudinary-video-player
    (不带版本号)或package.json中写
    "cloudinary-video-player": "^1.0.0"
    (插入号 = 最新兼容版本)。

Import Patterns

导入模式

  • ✅ Import Cloudinary instance:
    import { cld } from './cloudinary/config'
  • ✅ Import components:
    import { AdvancedImage, AdvancedVideo } from '@cloudinary/react'
  • ✅ Import plugins:
    import { responsive, lazyload, placeholder } from '@cloudinary/react'
  • For transformations and overlays, use only the exact paths in "Import reference: @cloudinary/url-gen" and the "Canonical overlay block" below. Do not guess subpaths (e.g.
    text
    and
    image
    are from
    qualifiers/source
    , not
    actions/overlay
    ).
  • ✅ 导入Cloudinary实例:
    import { cld } from './cloudinary/config'
  • ✅ 导入组件:
    import { AdvancedImage, AdvancedVideo } from '@cloudinary/react'
  • ✅ 导入插件:
    import { responsive, lazyload, placeholder } from '@cloudinary/react'
  • 转换和叠加层相关导入,仅使用「@cloudinary/url-gen导入对照表」和下文「标准叠加层代码块」中的精确路径。不要猜测子路径(例如
    text
    image
    qualifiers/source
    导入,而非
    actions/overlay
    )。

Import reference: @cloudinary/url-gen (use these exact paths only)

导入对照表:@cloudinary/url-gen(仅使用以下精确路径)

Rule: Do not invent or guess import paths for
@cloudinary/url-gen
. Use only the paths in the table and canonical block below. Copy the import statements exactly; do not derive paths (e.g.
@cloudinary/url-gen/overlay
exports only
source
text
and
image
are from
qualifiers/source
;
Position
is from
qualifiers/position
, not
positioning/Position
). Wrong paths cause "module not found" or "does not exist".
PurposeExact import
Cloudinary instance (config)
import { Cloudinary } from '@cloudinary/url-gen';
Resize (fill)
import { fill } from '@cloudinary/url-gen/actions/resize';
Resize (scale, for overlays)
import { scale } from '@cloudinary/url-gen/actions/resize';
Delivery format/quality
import { format, quality } from '@cloudinary/url-gen/actions/delivery';
Format qualifier (auto)
import { auto } from '@cloudinary/url-gen/qualifiers/format';
Quality qualifier (auto)
import { auto as autoQuality } from '@cloudinary/url-gen/qualifiers/quality';
Effects (e.g. blur)
import { blur } from '@cloudinary/url-gen/actions/effect';
Overlay source
import { source } from '@cloudinary/url-gen/actions/overlay';
Overlay text / image (source types)
import { text, image } from '@cloudinary/url-gen/qualifiers/source';
Overlay image transformation
import { Transformation } from '@cloudinary/url-gen/transformation/Transformation';
Position (overlay)
import { Position } from '@cloudinary/url-gen/qualifiers/position';
Gravity/compass
import { compass } from '@cloudinary/url-gen/qualifiers/gravity';
Text style (overlay)
import { TextStyle } from '@cloudinary/url-gen/qualifiers/textStyle';
Types
import type { CloudinaryImage, CloudinaryVideo } from '@cloudinary/url-gen';
Canonical overlay block (copy these imports and patterns exactly):
ts
// Overlay imports — text/image from qualifiers/source, NOT actions/overlay
import { source } from '@cloudinary/url-gen/actions/overlay';
import { text, image } from '@cloudinary/url-gen/qualifiers/source';
import { Position } from '@cloudinary/url-gen/qualifiers/position';
import { TextStyle } from '@cloudinary/url-gen/qualifiers/textStyle';
import { compass } from '@cloudinary/url-gen/qualifiers/gravity';
import { Transformation } from '@cloudinary/url-gen/transformation/Transformation';
import { scale } from '@cloudinary/url-gen/actions/resize';

// Text overlay (compass with underscores: 'south_east', 'center')
cld.image('id').overlay(
  source(text('Hello', new TextStyle('Arial', 60).fontWeight('bold')).textColor('white'))
    .position(new Position().gravity(compass('center')))
);

// Image overlay (logo/image with resize)
cld.image('id').overlay(
  source(image('logo').transformation(new Transformation().resize(scale().width(100))))
    .position(new Position().gravity(compass('south_east')).offsetX(20).offsetY(20))
);
  • Components (AdvancedImage, AdvancedVideo, plugins) come from
    @cloudinary/react
    , not from
    @cloudinary/url-gen
    .
  • Transformation actions and qualifiers (resize, delivery, effect, overlay, etc.) come from
    @cloudinary/url-gen/actions/*
    and
    @cloudinary/url-gen/qualifiers/*
    with the exact subpaths above.
  • If an import fails, verify the package version (
    @cloudinary/url-gen
    in package.json) and the Cloudinary URL-Gen SDK docs or Transformation Builder reference.
规则:不要自创或猜测
@cloudinary/url-gen
的导入路径,仅使用下表和标准代码块中的路径,严格复制导入语句,不要推导路径(例如
@cloudinary/url-gen/overlay
仅导出
source
——
text
image
来自**
qualifiers/source
Position
来自
qualifiers/position
**,而非
positioning/Position
)。错误的路径会导致「模块未找到」或「不存在」错误。
用途精确导入语句
Cloudinary实例(配置)
import { Cloudinary } from '@cloudinary/url-gen';
调整大小(fill)
import { fill } from '@cloudinary/url-gen/actions/resize';
调整大小(scale,用于叠加层)
import { scale } from '@cloudinary/url-gen/actions/resize';
交付格式/质量
import { format, quality } from '@cloudinary/url-gen/actions/delivery';
格式限定符(auto)
import { auto } from '@cloudinary/url-gen/qualifiers/format';
质量限定符(auto)
import { auto as autoQuality } from '@cloudinary/url-gen/qualifiers/quality';
效果(例如模糊)
import { blur } from '@cloudinary/url-gen/actions/effect';
叠加层源
import { source } from '@cloudinary/url-gen/actions/overlay';
叠加层文本/图片(源类型)
import { text, image } from '@cloudinary/url-gen/qualifiers/source';
叠加层图片转换
import { Transformation } from '@cloudinary/url-gen/transformation/Transformation';
位置(叠加层)
import { Position } from '@cloudinary/url-gen/qualifiers/position';
重力/方位
import { compass } from '@cloudinary/url-gen/qualifiers/gravity';
文本样式(叠加层)
import { TextStyle } from '@cloudinary/url-gen/qualifiers/textStyle';
类型
import type { CloudinaryImage, CloudinaryVideo } from '@cloudinary/url-gen';
标准叠加层代码块(严格复制以下导入和模式):
ts
// 叠加层导入 —— text/image来自qualifiers/source,不是actions/overlay
import { source } from '@cloudinary/url-gen/actions/overlay';
import { text, image } from '@cloudinary/url-gen/qualifiers/source';
import { Position } from '@cloudinary/url-gen/qualifiers/position';
import { TextStyle } from '@cloudinary/url-gen/qualifiers/textStyle';
import { compass } from '@cloudinary/url-gen/qualifiers/gravity';
import { Transformation } from '@cloudinary/url-gen/transformation/Transformation';
import { scale } from '@cloudinary/url-gen/actions/resize';

// 文本叠加层(compass参数用下划线:'south_east', 'center')
cld.image('id').overlay(
  source(text('Hello', new TextStyle('Arial', 60).fontWeight('bold')).textColor('white'))
    .position(new Position().gravity(compass('center')))
);

// 图片叠加层(logo/图片带resize)
cld.image('id').overlay(
  source(image('logo').transformation(new Transformation().resize(scale().width(100))))
    .position(new Position().gravity(compass('south_east')).offsetX(20).offsetY(20))
);
  • 组件(AdvancedImage、AdvancedVideo、插件)来自**
    @cloudinary/react
    **,不是
    @cloudinary/url-gen
  • 转换动作和限定符(resize、delivery、effect、overlay等)来自**
    @cloudinary/url-gen/actions/*
    @cloudinary/url-gen/qualifiers/*
    **,严格使用上述子路径。
  • 如果导入失败,请验证package.json中的
    @cloudinary/url-gen
    版本,参考Cloudinary URL-Gen SDK文档转换构建器参考

Creating Image & Video Instances

创建图片和视频实例

  • ✅ Create image instance:
    const img = cld.image(publicId)
  • ✅ Create video instance:
    const video = cld.video(publicId)
    (same pattern as images)
  • ✅ Public ID format: Use forward slashes for folders (e.g.,
    'folder/subfolder/image'
    )
  • ✅ Public IDs are case-sensitive and should not include file extensions
  • Sample assets: Cloudinary may provide sample assets under
    samples/
    . Assume they might not exist (users can delete them); always handle load errors and provide fallbacks (see Image gallery). When they exist, use them for examples and demos instead of requiring uploads first.
  • Sample public IDs that may be available (use for galleries, demos; handle onError if missing):
    • Images:
      samples/cloudinary-icon
      ,
      samples/two-ladies
      ,
      samples/food/spices
      ,
      samples/landscapes/beach-boat
      ,
      samples/bike
      ,
      samples/landscapes/girl-urban-view
      ,
      samples/animals/reindeer
      ,
      samples/food/pot-mussels
    • Video:
      samples/elephants
  • Default / most reliable: Start with
    samples/cloudinary-icon
    for a single image; use the list above for galleries or variety. Prefer uploaded assets when the user has them.
  • ✅ Examples:
    tsx
    const displayImage = cld.image('samples/cloudinary-icon');
    const displayVideo = cld.video('samples/elephants');
    // Gallery: e.g. ['samples/bike', 'samples/landscapes/beach-boat', 'samples/food/spices', ...]
  • ✅ 创建图片实例:
    const img = cld.image(publicId)
  • ✅ 创建视频实例:
    const video = cld.video(publicId)
    (和图片模式一致)
  • ✅ Public ID格式:文件夹用正斜杠分隔(例如
    'folder/subfolder/image'
  • ✅ Public ID区分大小写,不要包含文件扩展名
  • 示例资源:Cloudinary可能在
    samples/
    路径下提供示例资源,假设这些资源可能不存在(用户可以删除),请始终处理加载错误并提供 fallback(见图片画廊章节)。如果资源存在,可以用于示例和演示,不需要用户先上传。
  • 可用的示例Public ID(用于画廊、演示;如果不存在请处理onError):
    • 图片:
      samples/cloudinary-icon
      samples/two-ladies
      samples/food/spices
      samples/landscapes/beach-boat
      samples/bike
      samples/landscapes/girl-urban-view
      samples/animals/reindeer
      samples/food/pot-mussels
    • 视频:
      samples/elephants
  • 默认/最可靠的示例:单张图片优先用
    samples/cloudinary-icon
    ;画廊或需要多种资源时用上述列表。如果用户有自己上传的资源,优先使用上传的资源。
  • ✅ 示例:
    tsx
    const displayImage = cld.image('samples/cloudinary-icon');
    const displayVideo = cld.video('samples/elephants');
    // 画廊:例如 ['samples/bike', 'samples/landscapes/beach-boat', 'samples/food/spices', ...]

Transformation Patterns

转换模式

Image Transformations

图片转换

  • ✅ Chain transformations on image instance:
    tsx
    const img = cld.image('id')
      .resize(fill().width(800).height(600))
      .effect(blur(800))
      .delivery(format(auto()))
      .delivery(quality(autoQuality()));
  • ✅ Pass to component:
    <AdvancedImage cldImg={img} />
  • ✅ 在图片实例上链式调用转换方法:
    tsx
    const img = cld.image('id')
      .resize(fill().width(800).height(600))
      .effect(blur(800))
      .delivery(format(auto()))
      .delivery(quality(autoQuality()));
  • ✅ 传入组件:
    <AdvancedImage cldImg={img} />

Video Transformations

视频转换

  • ✅ Chain transformations on video instance (same pattern as images):
    tsx
    const video = cld.video('id')
      .resize(fill().width(800).height(600))
      .delivery(format(auto()));
  • ✅ Pass to component:
    <AdvancedVideo cldVid={video} />
  • ✅ Video transformations work the same way as image transformations
  • ✅ 在视频实例上链式调用转换方法(和图片模式一致):
    tsx
    const video = cld.video('id')
      .resize(fill().width(800).height(600))
      .delivery(format(auto()));
  • ✅ 传入组件:
    <AdvancedVideo cldVid={video} />
  • ✅ 视频转换和图片转换的用法完全一致

Transformation Best Practices

转换最佳实践

  • ✅ Format and quality must use separate
    .delivery()
    calls
  • ✅ Always end with auto format/quality:
    .delivery(format(auto())).delivery(quality(autoQuality()))
    unless user specifies a particular format or quality
  • ✅ Use
    gravity(auto())
    unless user specifies a focal point
  • ✅ Same transformation syntax works for both images and videos
  • ✅ 格式和质量必须使用独立的
    .delivery()
    调用
  • ✅ 除非用户指定特定格式或质量,否则转换末尾始终添加自动格式/质量:
    .delivery(format(auto())).delivery(quality(autoQuality()))
  • ✅ 除非用户指定焦点,否则使用
    gravity(auto())
  • ✅ 图片和视频使用相同的转换语法

Plugin Patterns

插件模式

  • When the user asks for lazy loading or responsive images: Use the Cloudinary plugins from
    @cloudinary/react
    responsive()
    ,
    lazyload()
    ,
    placeholder()
    — with
    AdvancedImage
    . Do not use only native
    loading="lazy"
    or CSS-only responsive; the Cloudinary plugins handle breakpoints, lazy loading, and placeholders for Cloudinary URLs.
  • ✅ Import plugins from
    @cloudinary/react
  • ✅ Pass plugins as array:
    plugins={[responsive(), lazyload(), placeholder()]}
  • ✅ Recommended plugin order:
    1. responsive()
      - First (handles breakpoints)
    2. placeholder()
      - Second (shows placeholder while loading)
    3. lazyload()
      - Third (delays loading until in viewport)
    4. accessibility()
      - Last (if needed)
  • ✅ Always add
    width
    and
    height
    attributes to prevent layout shift
  • ✅ Example:
    tsx
    <AdvancedImage
      cldImg={img}
      plugins={[responsive(), placeholder({ mode: 'blur' }), lazyload()]}
      width={800}
      height={600}
    />
  • 当用户需要懒加载或响应式图片时:使用
    @cloudinary/react
    提供的Cloudinary插件 ——
    responsive()
    lazyload()
    placeholder()
    搭配
    AdvancedImage
    使用。不要仅用原生
    loading="lazy"
    或纯CSS实现响应式,Cloudinary插件会针对Cloudinary URL处理断点、懒加载和占位符。
  • ✅ 从
    @cloudinary/react
    导入插件
  • ✅ 以数组形式传入插件:
    plugins={[responsive(), lazyload(), placeholder()]}
  • ✅ 推荐的插件顺序:
    1. responsive()
      - 第一个(处理断点)
    2. placeholder()
      - 第二个(加载时显示占位符)
    3. lazyload()
      - 第三个(进入视口才加载)
    4. accessibility()
      - 最后(如果需要)
  • ✅ 始终添加
    width
    height
    属性避免布局偏移
  • ✅ 示例:
    tsx
    <AdvancedImage
      cldImg={img}
      plugins={[responsive(), placeholder({ mode: 'blur' }), lazyload()]}
      width={800}
      height={600}
    />

Responsive Images Pattern

响应式图片模式

  • Responsive images: Use the Cloudinary
    responsive()
    plugin with
    fill()
    resize (not only CSS). Lazy loading: Use the Cloudinary
    lazyload()
    plugin with
    AdvancedImage
    (not only
    loading="lazy"
    ).
  • ✅ Use
    responsive()
    plugin with
    fill()
    resize
  • ✅ Combine with
    placeholder()
    and
    lazyload()
    plugins
  • ✅ Example:
    tsx
    const img = cld.image('id').resize(fill().width(800));
    <AdvancedImage 
      cldImg={img} 
      plugins={[responsive(), placeholder({ mode: 'blur' }), lazyload()]} 
      width={800}
      height={600}
    />
  • 响应式图片:使用Cloudinary
    responsive()
    插件搭配
    fill()
    调整大小(不要仅用CSS)。懒加载:使用Cloudinary
    lazyload()
    插件搭配
    AdvancedImage
    (不要仅用
    loading="lazy"
    )。
  • responsive()
    插件搭配
    fill()
    调整大小使用
  • ✅ 结合
    placeholder()
    lazyload()
    插件使用
  • ✅ 示例:
    tsx
    const img = cld.image('id').resize(fill().width(800));
    <AdvancedImage 
      cldImg={img} 
      plugins={[responsive(), placeholder({ mode: 'blur' }), lazyload()]} 
      width={800}
      height={600}
    />

Image gallery with lazy loading and responsive

带懒加载和响应式的图片画廊

  • When the user asks for an image gallery with lazy loading and responsive: Use Cloudinary plugins with
    AdvancedImage
    :
    responsive()
    ,
    lazyload()
    ,
    placeholder()
    (see Plugin Patterns). Use
    fill()
    resize with the responsive plugin. Add
    width
    and
    height
    to prevent layout shift.
  • Sample assets in galleries: Use the sample public IDs from "Creating Image & Video Instances" (e.g.
    samples/bike
    ,
    samples/landscapes/beach-boat
    ,
    samples/food/spices
    ,
    samples/two-ladies
    ,
    samples/landscapes/girl-urban-view
    ,
    samples/animals/reindeer
    ,
    samples/food/pot-mussels
    ,
    samples/cloudinary-icon
    ). Assume any sample might not exist — users can delete them. Start with one reliable sample (e.g.
    samples/cloudinary-icon
    ) or a short list; add onError handling and remove/hide failed images. Prefer uploaded assets when available (e.g. from UploadWidget) over samples.
  • Handle load errors: Use
    onError
    on
    AdvancedImage
    to hide or remove failed images (e.g. set state to filter out the publicId, or hide the parent). Provide user feedback (e.g. "Some images could not be loaded. Try uploading your own!") and upload functionality so users can add their own images.
  • Fallback: Default gallery list can be a subset of the sample list (e.g.
    ['samples/cloudinary-icon', 'samples/bike', 'samples/landscapes/beach-boat']
    ); when user uploads, append
    result.public_id
    . If an image fails to load, remove it from the list or hide it so the UI doesn't show broken images.
  • 当用户需要带懒加载和响应式的图片画廊时:搭配
    AdvancedImage
    使用Cloudinary插件
    responsive()
    lazyload()
    placeholder()
    (见插件模式)。
    fill()
    调整大小搭配响应式插件使用。添加
    width
    height
    避免布局偏移。
  • 画廊中的示例资源:使用「创建图片和视频实例」中的示例Public ID(例如
    samples/bike
    samples/landscapes/beach-boat
    samples/food/spices
    samples/two-ladies
    samples/landscapes/girl-urban-view
    samples/animals/reindeer
    samples/food/pot-mussels
    samples/cloudinary-icon
    )。假设任何示例都可能不存在——用户可以删除这些资源。从一个可靠的示例(例如
    samples/cloudinary-icon
    )或短列表开始;添加onError处理,移除/隐藏加载失败的图片。优先使用用户上传的资源(例如来自UploadWidget的资源)而非示例资源。
  • 处理加载错误:在
    AdvancedImage
    上使用
    onError
    隐藏或移除加载失败的图片(例如设置state过滤掉publicId,或隐藏父元素)。给用户反馈(例如「部分图片加载失败,你可以尝试上传自己的图片!」)并提供上传功能,让用户可以添加自己的图片。
  • 降级方案:默认画廊列表可以是示例列表的子集(例如
    ['samples/cloudinary-icon', 'samples/bike', 'samples/landscapes/beach-boat']
    );用户上传后将
    result.public_id
    追加到列表中。如果图片加载失败,从列表中移除或隐藏,避免UI显示损坏的图片。

Image Overlays (text or logos)

图片叠加层(文本或logo)

  • When the user asks for image overlays with text or logos: Use
    @cloudinary/url-gen
    overlay APIs. Copy imports and patterns from the "Import reference" table and "Canonical overlay block" in these rules. Do not import
    text
    or
    image
    from
    actions/overlay
    — they are from
    qualifiers/source
    ; only
    source
    is from
    actions/overlay
    .
  • Import
    source
    from
    actions/overlay
    ;
    text
    and
    image
    from
    qualifiers/source
    . Also:
    Position
    from
    qualifiers/position
    ,
    TextStyle
    from
    qualifiers/textStyle
    ,
    compass
    from
    qualifiers/gravity
    ,
    Transformation
    from
    transformation/Transformation
    ,
    scale
    from
    actions/resize
    .
  • compass() takes string values, with underscores:
    compass('center')
    ,
    compass('south_east')
    ,
    compass('north_west')
    . ❌ WRONG:
    compass(southEast)
    or
    'southEast'
    (no camelCase).
  • Overlay image: Use
    new Transformation()
    inside
    .transformation()
    :
    image('logo').transformation(new Transformation().resize(scale().width(100)))
    . ❌ WRONG:
    image('logo').transformation().resize(...)
    (
    .transformation()
    does not return a chainable object).
  • Text overlay:
    fontWeight
    goes on TextStyle:
    new TextStyle('Arial', 60).fontWeight('bold')
    .
    textColor
    goes on the text source (chained after
    text(...)
    ):
    text('Hello', new TextStyle('Arial', 60)).textColor('white')
    .
  • Position is chained after
    source(...)
    , not inside:
    source(image('logo').transformation(...)).position(new Position().gravity(compass('south_east')).offsetX(20).offsetY(20))
    .
  • Image overlay pattern:
    baseImage.overlay(source(image('id').transformation(new Transformation().resize(scale().width(100)))).position(new Position().gravity(compass('south_east')).offsetX(20).offsetY(20)))
    . (Import
    scale
    from
    @cloudinary/url-gen/actions/resize
    if needed.)
  • Text overlay pattern:
    baseImage.overlay(source(text('Your Text', new TextStyle('Arial', 60).fontWeight('bold')).textColor('white')).position(new Position().gravity(compass('center'))))
    .
  • ✅ Docs: React Image Transformations and transformation reference for overlay syntax.
  • 当用户需要添加文本或logo图片叠加层时:使用
    @cloudinary/url-gen
    的叠加层API。严格复制本规则中「导入对照表」和「标准叠加层代码块」的导入和模式。不要从
    actions/overlay
    导入
    text
    image
    ——它们来自**
    qualifiers/source
    **;仅
    source
    来自
    actions/overlay
  • 导入
    source
    actions/overlay
    导入;**
    text
    image
    qualifiers/source
    **导入。同时导入:
    Position
    qualifiers/position
    TextStyle
    qualifiers/textStyle
    compass
    qualifiers/gravity
    Transformation
    transformation/Transformation
    scale
    actions/resize
  • compass() 接收字符串参数,用下划线分隔:
    compass('center')
    compass('south_east')
    compass('north_west')
    。❌ 错误用法:
    compass(southEast)
    'southEast'
    (不要用驼峰命名)。
  • 叠加层图片:在
    .transformation()
    内使用
    new Transformation()
    image('logo').transformation(new Transformation().resize(scale().width(100)))
    。❌ 错误用法:
    image('logo').transformation().resize(...)
    .transformation()
    不返回可链式调用的对象)。
  • 文本叠加层
    fontWeight
    设置在TextStyle上:
    new TextStyle('Arial', 60).fontWeight('bold')
    textColor
    设置在文本源上(在
    text(...)
    后链式调用):
    text('Hello', new TextStyle('Arial', 60)).textColor('white')
  • Position 链式调用在**
    source(...)
    之后**,不是内部:
    source(image('logo').transformation(...)).position(new Position().gravity(compass('south_east')).offsetX(20).offsetY(20))
  • 图片叠加层模式
    baseImage.overlay(source(image('id').transformation(new Transformation().resize(scale().width(100)))).position(new Position().gravity(compass('south_east')).offsetX(20).offsetY(20)))
    。(如果需要从
    @cloudinary/url-gen/actions/resize
    导入
    scale
    。)
  • 文本叠加层模式
    baseImage.overlay(source(text('Your Text', new TextStyle('Arial', 60).fontWeight('bold')).textColor('white')).position(new Position().gravity(compass('center'))))
  • ✅ 文档:React图片转换和转换参考中的叠加层语法。

Upload Widget Pattern

上传组件模式

  • ✅ Use component:
    import { UploadWidget } from './cloudinary/UploadWidget'
Strict initialization pattern (always follow this exactly):
  1. Script in
    index.html
    (required):
html
<script src="https://upload-widget.cloudinary.com/global/all.js" async></script>
  1. Poll in useEffect until
    createUploadWidget
    is available
    (required): Use
    setInterval
    (e.g. every 100ms) to check
    typeof window.cloudinary?.createUploadWidget === 'function'
    . Only create the widget when this returns
    true
    . Clear the interval once ready.
  2. Add a timeout (e.g. 10 seconds) to stop polling and show an error state if the script never loads. Clear both interval and timeout in cleanup and when ready.
  3. Create widget once, store in a ref. Cleanup: clear interval, clear timeout, remove click listener.
Do NOT: Check only
window.cloudinary
(the function may not be attached yet); do a single check in
onload
(unreliable timing); skip
index.html
and rely only on dynamic injection; poll forever without a timeout.
  • ✅ Create unsigned upload preset in dashboard at
    settings/upload/presets
  • ✅ Add to
    .env
    :
    VITE_CLOUDINARY_UPLOAD_PRESET=your_preset_name
  • ✅ Handle callbacks:
    tsx
    <UploadWidget
      onUploadSuccess={(result) => {
        console.log('Public ID:', result.public_id);
      }}
      onUploadError={(error) => {
        console.error('Upload failed:', error);
      }}
    />
  • ✅ Upload result contains:
    public_id
    ,
    secure_url
    ,
    width
    ,
    height
    , etc.
For complete Upload Widget configuration options (all properties for
createUploadWidget
), see the official reference: https://cloudinary.com/documentation/upload_widget_reference.md
  • ✅ 使用组件:
    import { UploadWidget } from './cloudinary/UploadWidget'
严格初始化模式(始终遵循):
  1. index.html
    中引入脚本
    (必填):
html
<script src="https://upload-widget.cloudinary.com/global/all.js" async></script>
  1. 在useEffect中轮询直到
    createUploadWidget
    可用
    (必填):使用
    setInterval
    (例如每100ms)检测
    typeof window.cloudinary?.createUploadWidget === 'function'
    ,仅当返回
    true
    时才创建组件,准备就绪后清除定时器。
  2. 添加超时逻辑(例如10秒),如果脚本始终未加载完成则停止轮询并展示错误状态。在清理函数和准备就绪时同时清除定时器和超时器。
  3. 仅创建一次组件,存入ref中。清理逻辑:清除定时器、清除超时器、移除点击监听。
禁止操作:仅检测
window.cloudinary
(函数可能还未挂载);仅在
onload
中做单次检测(时机不可靠);不在
index.html
中引入脚本仅依赖动态注入;无超时无限轮询。
  • ✅ 在控制台的
    settings/upload/presets
    路径下创建未签名上传预设
  • ✅ 添加到
    .env
    VITE_CLOUDINARY_UPLOAD_PRESET=your_preset_name
  • ✅ 处理回调:
    tsx
    <UploadWidget
      onUploadSuccess={(result) => {
        console.log('Public ID:', result.public_id);
      }}
      onUploadError={(error) => {
        console.error('Upload failed:', error);
      }}
    />
  • ✅ 上传结果包含:
    public_id
    secure_url
    width
    height
    等字段。
如需完整的上传组件配置选项
createUploadWidget
的所有属性),查看官方参考:https://cloudinary.com/documentation/upload_widget_reference.md

Signed vs unsigned uploads (when to use which)

签名vs未签名上传(适用场景)

Unsigned uploads (simpler, no backend required):
  • Use when: Quick prototypes, low-risk apps, or when anyone with the preset name may upload.
  • Preset: Create an Unsigned upload preset in Cloudinary dashboard (Settings → Upload → Upload presets). Put preset name in
    .env
    as
    VITE_CLOUDINARY_UPLOAD_PRESET
    .
  • Client: Widget needs only
    cloudName
    and
    uploadPreset
    . No API key or secret; no backend.
  • Trade-off: Anyone who knows the preset name can upload. Use only when that is acceptable.
Signed uploads (more secure, backend required):
  • Use when: Production apps, authenticated users, or when you need to control who can upload.
  • Preset: Create a Signed upload preset in the dashboard. The backend generates a signature using your API secret; the client never sees the secret.
  • Client: Widget gets
    api_key
    (from your backend),
    uploadPreset
    , and an
    uploadSignature
    function that calls your backend for each upload. API secret stays on server only.
  • Trade-off: Requires a backend (Node/Express, Next.js API route, etc.) to sign requests. More secure; signature validates each upload.
Rule of thumb: Default to unsigned uploads unless the user explicitly asks for "secure" or "signed" uploads. Do not default to signed — it requires a running backend and will fail out of the box. Use signed only when the user explicitly requests secure/signed uploads or needs to restrict who can upload.
未签名上传(更简单,不需要后端):
  • 适用场景:快速原型、低风险应用,或者允许任何知道预设名称的用户上传的场景。
  • 预设:在Cloudinary控制台(设置 → 上传 → 上传预设)中创建未签名上传预设,将预设名称存入
    .env
    VITE_CLOUDINARY_UPLOAD_PRESET
    字段。
  • 客户端:组件仅需要
    cloudName
    uploadPreset
    ,不需要API密钥或secret,不需要后端。
  • 权衡:任何知道预设名称的人都可以上传,仅在可接受该风险时使用。
签名上传(更安全,需要后端):
  • 适用场景:生产应用、需要认证的用户,或者需要控制上传权限的场景。
  • 预设:在控制台创建签名上传预设。后端使用你的API secret生成签名,客户端永远不会获取到secret。
  • 客户端:组件从你的后端获取
    api_key
    uploadPreset
    ,以及
    uploadSignature
    函数(每次上传时调用后端获取签名)。API secret仅保存在服务端。
  • 权衡:需要后端(Node/Express、Next.js API路由等)对请求签名,安全性更高,签名会校验每次上传的合法性。
经验法则默认使用未签名上传,除非用户明确要求「安全」或「签名」上传。不要默认使用签名上传——它需要运行中的后端,开箱即会失败。仅当用户明确要求安全/签名上传或需要限制上传权限时才使用签名上传。

Secure (Signed) Uploads

安全(签名)上传

When to use: Production apps, authenticated users, or when you need to control who can upload (more secure than unsigned).
Golden rules:
  1. Never expose or commit the API secret (server-only)
  2. Use
    server/.env
    in
    .gitignore
    for API key/secret
  3. API key can be sent to client; API secret must stay server-only
Quick implementation:
  • Use
    uploadSignature
    as function (not
    signatureEndpoint
    )
  • Fetch
    api_key
    from server before creating widget
  • Include
    uploadPreset
    in widget config
  • Server includes
    upload_preset
    in signed params
  • Use Cloudinary Node.js SDK v2 on server
For complete implementation patterns, client/server code examples, and troubleshooting, see references/signed-uploads.md
适用场景:生产应用、认证用户,或者需要控制上传权限的场景(比未签名更安全)。
黄金规则:
  1. 永远不要暴露或提交API secret(仅服务端使用)
  2. 将服务端的
    .env
    加入
    .gitignore
    ,存储API密钥和secret
  3. API key可以发送到客户端;API secret必须仅保存在服务端
快速实现:
  • uploadSignature
    作为函数传入(不要用
    signatureEndpoint
  • 创建组件前从服务端获取
    api_key
  • 组件配置中包含
    uploadPreset
  • 服务端在签名参数中包含
    upload_preset
  • 服务端使用Cloudinary Node.js SDK v2版本
完整实现模式、客户端/服务端代码示例和问题排查,查看 references/signed-uploads.md

Video Patterns

视频模式

  • Display a video → Use AdvancedVideo (
    @cloudinary/react
    ). It just displays a video (with optional transformations). Not a full player.
  • A video player → Use Cloudinary Video Player (
    cloudinary-video-player
    ). That is the actual player (styled UI, controls, playlists, etc.).
  • 展示视频 → 使用AdvancedVideo
    @cloudinary/react
    ),仅用于展示视频(可搭配转换),不是完整的播放器。
  • 视频播放器 → 使用Cloudinary Video Player
    cloudinary-video-player
    ),是完整的播放器(带样式UI、控制栏、播放列表等)。

⚠️ IMPORTANT: Two Different Approaches

⚠️ 重要:两种不同的方案

1. AdvancedVideo (
@cloudinary/react
) — For displaying a video
  • React component similar to
    AdvancedImage
    ; just displays a video with Cloudinary transformations
  • Not a full "player" — it's video display (native HTML5 video with optional controls)
  • Use when: user wants to show/display a video. Works with
    cld.video()
    like images with
    cld.image()
2. Cloudinary Video Player (
cloudinary-video-player
) — The player
  • Full-featured video player (styled UI, controls, playlists). Use when the user asks for a "video player."
  • Use imperative video element only (create with document.createElement, append to container ref); do not pass a React-managed
    <video ref>
    . See "Cloudinary Video Player (The Player)" below.
1. AdvancedVideo
@cloudinary/react
)—— 用于展示视频
  • AdvancedImage
    类似的React组件,仅用于展示带Cloudinary转换的视频
  • 不是完整的「播放器」—— 仅做视频展示(原生HTML5 video,可选带控制栏)
  • 适用场景:用户仅需要展示/显示视频,和
    cld.image()
    用法一样,搭配
    cld.video()
    使用
2. Cloudinary Video Player
cloudinary-video-player
)—— 完整播放器
  • 全功能视频播放器(带样式UI、控制栏、播放列表),用户明确要求「视频播放器」时使用。
  • 仅使用命令式创建video元素(用document.createElement创建,追加到容器ref);不要传入React管理的
    <video ref>
    ,见下文「Cloudinary视频播放器」章节。

AdvancedVideo (React SDK - For Displaying a Video)

AdvancedVideo(React SDK - 用于展示视频)

  • Purpose: Display a video with Cloudinary transformations (resize, effects, etc.). It is not a full player — it is for showing a video. For a player, use Cloudinary Video Player.
  • Package:
    @cloudinary/react
    (same as AdvancedImage)
  • Import:
    import { AdvancedVideo } from '@cloudinary/react'
  • NO CSS IMPORT NEEDED: AdvancedVideo uses native HTML5 video - no CSS import required
  • WRONG:
    import '@cloudinary/react/dist/cld-video-player.css'
    (this path doesn't exist)
  • Create video instance:
    const video = cld.video(publicId)
    (like
    cld.image()
    )
  • Apply transformations: Chain transformations like images:
    tsx
    const video = cld.video('video-id')
      .resize(fill().width(800).height(600))
      .delivery(format(auto()));
  • Use component:
    tsx
    <AdvancedVideo
      cldVid={video}
      controls
      autoplay
      muted
    />
  • Documentation: https://cloudinary.com/documentation/react_video_transformations.md
  • 用途:展示带Cloudinary转换(调整大小、效果等)的视频,不是完整播放器,仅用于展示视频。需要播放器请使用Cloudinary Video Player。
  • @cloudinary/react
    (和AdvancedImage同包)
  • 导入
    import { AdvancedVideo } from '@cloudinary/react'
  • 不需要导入CSS:AdvancedVideo使用原生HTML5 video,不需要导入CSS
  • 错误用法
    import '@cloudinary/react/dist/cld-video-player.css'
    (该路径不存在)
  • 创建视频实例
    const video = cld.video(publicId)
    (和
    cld.image()
    用法一致)
  • 应用转换:和图片一样链式调用转换方法:
    tsx
    const video = cld.video('video-id')
      .resize(fill().width(800).height(600))
      .delivery(format(auto()));
  • 组件用法
    tsx
    <AdvancedVideo
      cldVid={video}
      controls
      autoplay
      muted
    />
  • 文档https://cloudinary.com/documentation/react_video_transformations.md

Cloudinary Video Player (The Player)

Cloudinary 视频播放器(完整播放器)

Use when the user asks for a video player (styled UI, controls, playlists). For just displaying a video, use AdvancedVideo instead.
Critical rule: Imperative element only
  • ❌ Do NOT pass React-managed
    <video ref>
    (causes removeChild errors)
  • ✅ Use
    document.createElement('video')
    , append to container ref, pass to
    videoPlayer(el, ...)
Quick setup:
  • Package:
    npm install cloudinary-video-player
  • Import:
    import { videoPlayer } from 'cloudinary-video-player'
    and CSS
  • Source:
    player.source({ publicId })
    (object, not string)
  • Cleanup:
    player.dispose()
    then
    if (el.parentNode) el.parentNode.removeChild(el)
  • Always include
    posterOptions: { transformation: { startOffset: '0' }, posterColor: '#0f0f0f' }
For complete implementation pattern, cleanup, error handling, and troubleshooting, see references/video-player.md
用户明确要求视频播放器(带样式UI、控制栏、播放列表)时使用。仅需要展示视频请用AdvancedVideo。
核心规则:仅使用命令式元素
  • ❌ 不要传入React管理的
    <video ref>
    (会导致removeChild错误)
  • ✅ 使用
    document.createElement('video')
    ,追加到容器ref,传入
    videoPlayer(el, ...)
快速初始化:
  • 安装包:
    npm install cloudinary-video-player
  • 导入:
    import { videoPlayer } from 'cloudinary-video-player'
    和对应的CSS
  • 设置源:
    player.source({ publicId })
    (传入对象,不是字符串)
  • 清理:
    player.dispose()
    然后
    if (el.parentNode) el.parentNode.removeChild(el)
  • 始终添加
    posterOptions: { transformation: { startOffset: '0' }, posterColor: '#0f0f0f' }
完整实现模式、清理逻辑、错误处理和问题排查,查看 references/video-player.md

When to Use Which?

适用场景对比

  • Use AdvancedVideo when: User wants to display or show a video (no full player). It just displays a video with transformations.
  • Use Cloudinary Video Player when: User asks for a video player — the actual player with styled UI, controls, and optional features (playlists, ads, etc.).
  • 使用AdvancedVideo:用户仅需要展示显示视频(不需要完整播放器),仅展示带转换的视频。
  • 使用Cloudinary Video Player:用户明确要求视频播放器—— 带样式UI、控制栏和可选功能(播放列表、广告等)的完整播放器。

TypeScript Patterns

TypeScript模式

Essential TypeScript usage:
  • Type imports:
    import type { CloudinaryImage, CloudinaryVideo } from '@cloudinary/url-gen'
  • Upload results: Define
    CloudinaryUploadResult
    interface
  • Environment variables: Create
    vite-env.d.ts
    with
    ImportMetaEnv
    interface
  • Avoid
    any
    : Use proper interfaces or
    unknown
    with type guards
  • Type refs:
    useRef<HTMLVideoElement>(null)
    ,
    useRef<unknown>(null)
    for widgets
For complete TypeScript patterns, type guards, ref typing, and error solutions, see references/typescript-patterns.md
核心TypeScript用法:
  • 类型导入:
    import type { CloudinaryImage, CloudinaryVideo } from '@cloudinary/url-gen'
  • 上传结果:定义
    CloudinaryUploadResult
    接口
  • 环境变量:创建
    vite-env.d.ts
    ,定义
    ImportMetaEnv
    接口
  • 避免
    any
    :使用合适的接口或带类型守卫的
    unknown
  • ref类型:组件用
    useRef<HTMLVideoElement>(null)
    ,部件用
    useRef<unknown>(null)
完整TypeScript模式、类型守卫、ref类型定义和错误解决方案,查看 references/typescript-patterns.md

Best Practices

最佳实践

  • ✅ Always use
    fill()
    resize with automatic gravity for responsive images
  • ✅ Always end transformations with
    .delivery(format(auto())).delivery(quality(autoQuality()))
    unless the user specifies a format or quality
  • ✅ Use
    placeholder()
    and
    lazyload()
    plugins together
  • ✅ Always add
    width
    and
    height
    attributes to
    AdvancedImage
  • ✅ Store
    public_id
    from upload success, not full URL
  • ✅ Video player: use imperative element only; dispose in useLayoutEffect cleanup and remove element with
    if (el.parentNode) el.parentNode.removeChild(el)
    ; always include
    posterOptions
    with
    transformation: { startOffset: '0' }
    and
    posterColor: '#0f0f0f'
    for reliable poster display
  • ✅ Use TypeScript for better autocomplete and error catching
  • ✅ Prefer
    unknown
    over
    any
    when types aren't available
  • ✅ Use type guards for runtime type checking
  • ✅ Define interfaces for Cloudinary API responses
  • ✅ Create
    vite-env.d.ts
    for environment variable typing
  • ✅ Use proper HTML element types for refs

  • ✅ 响应式图片始终使用
    fill()
    调整大小搭配自动重力
  • ✅ 除非用户指定格式或质量,转换末尾始终添加
    .delivery(format(auto())).delivery(quality(autoQuality()))
  • ✅ 搭配使用
    placeholder()
    lazyload()
    插件
  • ✅ 始终给
    AdvancedImage
    添加
    width
    height
    属性
  • ✅ 上传成功后存储
    public_id
    ,不要存储完整URL
  • ✅ 视频播放器:仅使用命令式元素;在useLayoutEffect清理函数中dispose并使用
    if (el.parentNode) el.parentNode.removeChild(el)
    移除元素;始终包含
    posterOptions
    ,设置
    transformation: { startOffset: '0' }
    posterColor: '#0f0f0f'
    确保封面可靠展示
  • ✅ 使用TypeScript获得更好的自动补全和错误捕获
  • ✅ 类型未知时优先使用
    unknown
    而非
    any
  • ✅ 使用类型守卫做运行时类型检查
  • ✅ 为Cloudinary API响应定义接口
  • ✅ 创建
    vite-env.d.ts
    为环境变量提供类型
  • ✅ 为ref使用正确的HTML元素类型

⚠️ COMMON ERRORS & SOLUTIONS

⚠️ 常见错误与解决方案

For detailed error solutions and troubleshooting, see references/troubleshooting.md
详细的错误解决方案和问题排查,查看 references/troubleshooting.md

Quick Error Reference

快速错误参考

Environment Variable Errors

环境变量错误

  • Env vars: Wrong prefix, missing VITE_, not restarted → Use correct bundler prefix, restart, clear cache
  • Imports: Wrong package/path → Use exact paths from Import reference table
  • Upload Widget: "createUploadWidget is not a function" → Poll with setInterval, don't check only
    window.cloudinary
  • Transformations: Not working → Chain properly, use v2 syntax, separate format/quality
  • Video Player: removeChild errors → Use imperative element (createElement), not React ref
  • TypeScript: Type errors → See references/typescript-patterns.md
  • Signed uploads: Invalid signature → See references/signed-uploads.md
  • 环境变量:前缀错误、缺失VITE_、未重启服务 → 使用正确的打包工具前缀、重启服务、清除缓存
  • 导入错误:包/路径错误 → 使用导入对照表中的精确路径
  • 上传组件:"createUploadWidget is not a function" → 使用setInterval轮询,不要仅检测
    window.cloudinary
  • 转换:不生效 → 正确链式调用、使用v2语法、分开设置格式/质量
  • 视频播放器:removeChild错误 → 使用命令式元素(createElement),不要用React ref
  • TypeScript:类型错误 → 查看 references/typescript-patterns.md
  • 签名上传:签名无效 → 查看 references/signed-uploads.md

Most Common Issues

最高频问题

  1. Environment variables undefined: Clear
    node_modules/.vite/
    , restart dev server, hard refresh browser
  2. "createUploadWidget is not a function": Poll with
    setInterval
    until
    typeof window.cloudinary?.createUploadWidget === 'function'
  3. Wrong imports: Use ONLY paths from Import reference table - don't guess subpaths
  4. Upload fails: Check preset exists, is Unsigned (for unsigned), dev server restarted
  5. Video player errors: Use imperative element only, include posterOptions
  6. Overlay issues: Import
    text
    /
    image
    from
    qualifiers/source
    , not
    actions/overlay
For complete troubleshooting with detailed solutions, see references/troubleshooting.md
  1. 环境变量未定义:清除
    node_modules/.vite/
    、重启开发服务、强制刷新浏览器
  2. "createUploadWidget is not a function":用
    setInterval
    轮询直到
    typeof window.cloudinary?.createUploadWidget === 'function'
  3. 导入错误:仅使用导入对照表中的路径——不要猜测子路径
  4. 上传失败:检查预设是否存在、是否为未签名模式(未签名上传场景)、开发服务是否重启
  5. 视频播放器错误:仅使用命令式元素、包含posterOptions
  6. 叠加层问题:从
    qualifiers/source
    导入
    text
    /
    image
    ,不要从
    actions/overlay
    导入
完整的问题排查和详细解决方案,查看 references/troubleshooting.md