关于 cloudinary-react
cloudinary-react 是面向 React 开发者的 Cloudinary 技能,提供图片、视频和上传功能的实现模式与常见错误排查。内容涵盖不同打包工具的环境变量配置、上传组件、转换及视频播放器。
主要功能
- 01配置可复用的 Cloudinary 实例
- 02接入 Upload Widget
- 03实现签名与无签名上传
- 04应用图片和视频转换
- 05排查环境变量及播放器问题
适用场景
- 01在 React 应用中显示转换后的图片
- 02为应用添加 Cloudinary 上传组件
- 03排查上传组件或视频播放器故障
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cloudinary-devs/ skills --skill cloudinary-react安装后在 Claude Code 中输入/cloudinary-react确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/signed-uploads.mdtroubleshooting.mdtypescript-patterns.mdvideo-player.md详细介绍
Cloudinary React 技能
适用场景
- 用户在 React 应用(Vite、Create React App、Parcel 等)中构建或调试 Cloudinary 时。
- 实现或修复以下功能时:Upload Widget、AdvancedImage/AdvancedVideo、转换、叠加层、图片库、视频播放器,或签名/非签名上传。
- 用户遇到诸如“createUploadWidget is not a function”、从
@cloudinary/url-gen导入错误、上传预设问题或视频播放器 DOM 错误等问题时。
快速开始
最常见的操作:
- 设置:创建包含
cld实例的配置文件(参见“项目设置”部分) - 显示图片:
const img = cld.image('id').resize(fill().width(800)); <AdvancedImage cldImg={img} /> - Upload Widget:在 index.html 中添加脚本,并在 useEffect 中轮询
createUploadWidget - 图片叠加层:使用
source(text(...))或source(image(...))——确切路径请参见“导入参考表” - 签名上传:参见 references/signed-uploads.md
- 故障排除:参见 references/troubleshooting.md
TypeScript:参见 references/typescript-patterns.md 视频播放器:参见 references/video-player.md
说明
在 React 中协助使用 Cloudinary 时,请遵循以下模式和规则。使用指定的确切导入路径和代码模式;不要猜测子路径或臆造 API。
Cloudinary React SDK 模式与常见错误
范围:这些规则适用于使用浏览器 Upload Widget 的 React(Web)。默认使用 Vite(create-cloudinary-react 使用 Vite)。这些规则也适用于其他打包器(Create React App、Next.js、Parcel 等):只有读取环境变量的方式会变化;请参见下文的**“其他打包器(非 Vite)”。仅需规则的用户:请参见“项目设置(仅规则/不使用 CLI)”**,了解可复用的 Cloudinary 实例、环境变量、Upload Widget(非签名/签名)和视频播放器。关于 React Native 上传(包括签名上传),请参见:https://cloudinary.com/documentation/react_native_image_and_video_upload?install_source=skillspack&referrer=react-skill#signed_upload — 同样遵循“绝不暴露密钥,在后端生成签名”的原则,但 React Native 使用 upload() 方法,且后端 SDK 的使用方式有所不同。
官方文档
如需完整的 API 参考和所有配置选项,请通过 llms.txt 查阅 Cloudinary 文档:https://cloudinary.com/documentation/llms.txt?install_source=skillspack&referrer=react-skill
此技能提供 React 模式和常见错误的说明。如需了解详细的 API 选项(例如所有 Upload Widget 配置属性、Video Player API 方法),请使用 llms.txt 提供的文档 Markdown 文件。
关键参考资料
- 转换规则:https://cloudinary.com/documentation/cloudinary_transformation_rules.md?install_source=skillspack&referrer=react-skill
- 转换参考:https://cloudinary.com/documentation/transformation_reference.md?install_source=skillspack&referrer=react-skill
- React 图像转换与插件:https://cloudinary.com/documentation/react_image_transformations.md?install_source=skillspack&referrer=react-skill#plugins
- React 视频转换:https://cloudinary.com/documentation/react_video_transformations.md?install_source=skillspack&referrer=react-skill
- 上传组件参考(完整配置选项):https://cloudinary.com/documentation/upload_widget_reference.md?install_source=skillspack&referrer=react-skill
- 上传组件(签名上传):https://cloudinary.com/documentation/upload_widget.md?install_source=skillspack&referrer=react-skill#signed_uploads
- Cloudinary 视频播放器(独立播放器):https://cloudinary.com/documentation/cloudinary_video_player.md?install_source=skillspack&referrer=react-skill
- 视频播放器 API 参考(完整 API 方法):https://cloudinary.com/documentation/video_player_api_reference.md?install_source=skillspack&referrer=react-skill
- 视频播放器 React 教程:https://cloudinary.com/documentation/video_player_react_tutorial.md?install_source=skillspack&referrer=react-skill
- 在 Next.js 中上传资源(后端签名):https://cloudinary.com/documentation/upload_assets_in_nextjs_tutorial.md?install_source=skillspack&referrer=react-skill
- Cloudinary Node.js SDK(服务端签名)——使用 v2:
import { v2 as cloudinary } from 'cloudinary';不要使用 v1(例如 1.47.0)。https://cloudinary.com/documentation/node_integration.md?install_source=skillspack&referrer=react-skill - React Native 图像和视频上传(签名):https://cloudinary.com/documentation/react_native_image_and_video_upload.md?install_source=skillspack&referrer=react-skill#signed_upload
- 创建转换时,务必查阅官方转换规则
- 仅使用转换参考中官方支持的参数
**@cloudinary/url-gen 黄金法则:**生成叠加层或转换代码时,只使用这些规则中的“导入参考”表和“规范叠加层代码块”。严格照抄其中的导入路径和用法模式;不要猜测或推导路径(例如,text 和 image 来自 qualifiers/source,而不是 actions/overlay;Position 来自 qualifiers/position,而不是 positioning/Position)。
📋 模式(正确做法)
项目设置(仅使用规则/不使用 CLI)
如果用户未使用 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 实例:
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. 上传组件(未签名,从头开始)
严格遵循以下模式:
- 在
index.html中添加脚本(必需):在index.html中添加<script src="https://upload-widget.cloudinary.com/global/all.js" async></script>。不要只依赖从 React 动态注入脚本——这种方式不可靠。 - 在 useEffect 中轮询(必需):在
useEffect中使用setInterval轮询(例如每 100ms 一次),直到typeof window.cloudinary?.createUploadWidget === 'function'。只有此时才能创建组件。仅检查一次(即使在onload中检查)也不可靠,因为window.cloudinary可能先于createUploadWidget挂载而存在。 - 添加超时:设置超时时间(例如 10 秒),如果脚本始终未加载,则停止轮询并显示错误。清理时清除 interval 和 timeout。
- 只创建一次组件:当
createUploadWidget可用时,创建组件并将其存储在 ref 中。清除 interval 和 timeout。传入以下选项:{ cloudName, uploadPreset, sources: ['local', 'camera', 'url'], multiple: false }。 - 点击时打开:为按钮添加点击监听器,调用
widgetRef.current?.open()。在 useEffect 清理时移除监听器。
❌ 不要:只检查 window.cloudinary(这还不够);只在 onload 中检查一次(不可靠);跳过 index.html 中的脚本;没有超时地无限轮询。
- 签名上传:不要只使用
uploadPreset;请采用“安全(签名)上传”中的模式(将 uploadSignature 用作函数,获取 api_key,并在服务端的签名中包含 upload_preset)。
4. 视频播放器
- 只使用命令式视频元素(使用 document.createElement 创建,添加到容器 ref 中,并传入 videoPlayer)。完整模式请参阅“Cloudinary 视频播放器(The Player)”。
5. 仅查看规则的用户摘要
- 环境变量:使用打包工具的客户端环境变量前缀和访问方式(Vite:
VITE_+import.meta.env.VITE_*;如果不是 Vite,请参阅“其他打包工具”)。 - 可复用实例:创建一个配置文件,从
@cloudinary/url-gen创建并导出cld(以及可选的uploadPreset),并在所有地方使用它。 - 上传组件:在 index.html 中添加脚本(必需);在 useEffect 中轮询,直到
createUploadWidget是一个函数,然后创建一次组件并存储在 ref 中;未签名 = cloudName + uploadPreset;已签名 = 使用 uploadSignature 函数和后端。 - 视频播放器:使用命令式视频元素(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 实例、组件选项、视频播放器)相同。
环境变量
- 默认: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:- 清除 Vite 缓存:
rm -rf node_modules/.vite/(或删除node_modules/.vite文件夹) - 重启开发服务器:
npm run dev - 强制刷新浏览器:Cmd+Shift+R(Mac)或 Ctrl+Shift+F5(Windows/Linux)
- 如果仍为空,请使用静态配置作为临时解决方案(创建
cloudinaryConfig.ts,在开发时写入硬编码的云名称,之后再切换回环境变量),并参阅常见错误 →“VITE_ 前缀必需,否则环境变量为 undefined”
- 清除 Vite 缓存:
其他打包工具(非 Vite)
- 只有环境变量的访问方式会变化。 其他所有模式(可复用的
cld、Upload Widget、Video Player、叠加层、签名上传)都与打包工具无关。 - 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等访问。服务端可使用不带NEXT_PUBLIC_的process.env.CLOUDINARY_*。 - Parcel / 其他:查阅打包工具的文档,了解“向客户端公开环境变量”的方法(通常需要前缀或允许列表)。使用相应前缀和文档规定的访问方式(例如
process.env.*)。 - 配置文件:在
src/cloudinary/config.ts(或对应文件)中,使用用户所用打包工具的环境变量 API 读取云名称和上传预设(例如 CRA 使用process.env.REACT_APP_CLOUDINARY_CLOUD_NAME)。new Cloudinary({ cloud: { cloudName } })和导出内容保持不变;只有读取环境变量的代码行需要更改。
上传预设
- 未签名 = 仅客户端上传(无需后端)。已签名 = 需要后端,安全性更高。关于如何选择,请参阅下文的**“已签名与未签名上传”**。
- ✅ 创建未签名上传预设(用于简单的客户端上传):https://console.cloudinary.com/app/settings/upload/presets
- ✅ 在
.env中设置预设:VITE_CLOUDINARY_UPLOAD_PRESET=your-preset-name - ✅ 在代码中使用:
import { uploadPreset } from './cloudinary/config' - ⚠️ 如果缺少上传预设,Upload Widget 会显示错误消息
- ⚠️ 客户端使用时,上传预设必须设为“Unsigned”模式(无需 API 密钥/密钥)
- 未签名上传失败时:首先检查用户是否已配置上传预设:
.env中是否设置了VITE_CLOUDINARY_UPLOAD_PRESET?(必须与预设名称完全一致)- 控制台的 Settings → Upload → Upload presets 下是否存在该预设?
- 该预设是否设为Unsigned(而非 Signed)?
- 添加或更新
.env后是否重启了开发服务器?
安装 Cloudinary 包
- ✅ 安装最新版本:添加 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 [email protected]或"cloudinary-video-player": "1.2.3"(固定到确切版本)——这些版本可能不存在,并导致安装失败。 - ✅ 正确:
npm install cloudinary-video-player(不指定版本),或在 package.json 中使用:"cloudinary-video-player": "^1.0.0"(插入符号表示最新的兼容版本)。
导入模式
- ✅ 导入 Cloudinary 实例:
import { cld } from './cloudinary/config' - ✅ 导入组件:
import { AdvancedImage, AdvancedVideo } from '@cloudinary/react' - ✅ 导入插件:
import { responsive, lazyload, placeholder } from '@cloudinary/react' - ✅ 对于转换和叠加层,仅使用“Import reference: @cloudinary/url-gen”和下面“Canonical overlay block”中的准确路径。不要猜测子路径(例如,
text和image来自qualifiers/source,而不是actions/overlay)。
导入参考:@cloudinary/url-gen(仅使用这些确切路径)
**规则:**不要为 @cloudinary/url-gen 发明或猜测导入路径。仅使用下表和规范代码块中的路径。请原样复制导入语句;不要推导路径(例如,@cloudinary/url-gen/overlay 仅导出 source——text 和 image 来自 qualifiers/source;Position 来自 qualifiers/position,而非 positioning/Position)。路径错误会导致“找不到模块”或“不存在”。
| 用途 | 确切导入 |
|---|---|
| Cloudinary 实例(配置) | import { Cloudinary } from '@cloudinary/url-gen'; |
| 调整大小(填充) | import { fill } from '@cloudinary/url-gen/actions/resize'; |
| 调整大小(缩放,用于叠加) | import { scale } from '@cloudinary/url-gen/actions/resize'; |
| 交付格式/质量 | import { format, quality } from '@cloudinary/url-gen/actions/delivery'; |
| 格式限定符(自动) | import { auto } from '@cloudinary/url-gen/qualifiers/format'; |
| 质量限定符(自动) | 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'; |
规范叠加代码块(原样复制这些导入和模式):
// 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))
);
- 组件(AdvancedImage、AdvancedVideo、插件)来自
@cloudinary/react,而非@cloudinary/url-gen。 - 变换操作和限定符(调整大小、交付、效果、叠加等)来自
@cloudinary/url-gen/actions/*和@cloudinary/url-gen/qualifiers/*,请使用上方列出的确切子路径。 - 如果导入失败,请检查软件包版本(package.json 中的
@cloudinary/url-gen),并查阅 Cloudinary URL-Gen SDK 文档 或 Transformation Builder 参考。
创建图像和视频实例
- ✅ 创建图像实例:
const img = cld.image(publicId) - ✅ 创建视频实例:
const video = cld.video(publicId)(与图像使用相同模式) - ✅ Public ID 格式:文件夹之间使用正斜杠(例如,
'folder/subfolder/image') - ✅ Public ID 区分大小写,且不应包含文件扩展名
- ✅ 示例资源:Cloudinary 可能会在
samples/下提供示例资源。请假设这些资源可能不存在(用户可以将其删除);始终处理加载错误并提供回退方案(参见 Image gallery)。如果这些资源存在,请在示例和演示中使用它们,而不是要求先上传资源。 - ✅ 可能可用的示例 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开始;图库或需要多样性时使用上方列表。如果用户已有上传资源,优先使用这些资源。 - ✅ 示例:
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('id') .resize(fill().width(800).height(600)) .effect(blur(800)) .delivery(format(auto())) .delivery(quality(autoQuality())); - ✅ 传递给组件:
<AdvancedImage cldImg={img} />
视频转换
- ✅ 在视频实例上链式调用转换(与图像的模式相同):
const video = cld.video('id') .resize(fill().width(800).height(600)) .delivery(format(auto())); - ✅ 传递给组件:
<AdvancedVideo cldVid={video} /> - ✅ 视频转换的工作方式与图像转换相同
转换最佳实践
- ✅ 格式和质量必须使用单独的
.delivery()调用 - ✅ 除非用户指定特定格式或质量,否则始终以自动格式和质量结尾:
.delivery(format(auto())).delivery(quality(autoQuality())) - ✅ 除非用户指定焦点,否则使用
gravity(auto()) - ✅ 图像和视频使用相同的转换语法
插件模式
- ✅ 当用户要求延迟加载或响应式图像时:使用
@cloudinary/react中的 Cloudinary 插件——responsive()、lazyload()、placeholder()——并搭配AdvancedImage。不要只使用原生的loading="lazy"或仅通过 CSS 实现响应式;Cloudinary 插件可处理 Cloudinary URL 的断点、延迟加载和占位图。 - ✅ 从
@cloudinary/react导入插件 - ✅ 将插件作为数组传递:
plugins={[responsive(), lazyload(), placeholder()]} - ✅ 建议的插件顺序:
responsive()- 第一(处理断点)placeholder()- 第二(加载时显示占位图)lazyload()- 第三(进入视口后才延迟加载)accessibility()- 最后(如有需要)
- ✅ 始终添加
width和height属性,以防止布局偏移 - ✅ 示例:
<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()插件结合使用 - ✅ 示例:
const img = cld.image('id').resize(fill().width(800)); <AdvancedImage cldImg={img} plugins={[responsive(), placeholder({ mode: 'blur' }), lazyload()]} width={800} height={600} />
使用延迟加载和响应式布局的图像库
- ✅ 当用户要求创建带延迟加载和响应式布局的图像库时:将 Cloudinary 插件与
AdvancedImage搭配使用:responsive()、lazyload()、placeholder()(参见“插件模式”)。使用fill()调整大小,并搭配响应式插件。添加width和height,以防止布局偏移。 - ✅ 图像库中的示例资源:使用“创建图像和视频实例”中的示例公共 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隐藏或移除加载失败的图像(例如,设置状态以过滤掉该公共 ID,或隐藏其父元素)。向用户提供反馈(例如“部分图像无法加载。请尝试上传自己的图像!”),并提供上传功能,让用户可以添加自己的图像。 - ✅ 回退方案:默认图像库列表可以是示例列表的子集(例如
['samples/cloudinary-icon', 'samples/bike', 'samples/landscapes/beach-boat']);用户上传时,将result.public_id追加到列表中。如果图像加载失败,则从列表中移除或将其隐藏,避免 UI 显示损坏的图像。
图像叠加层(文本或徽标)
- ✅ 当用户要求添加文本或徽标图像叠加层时:使用
@cloudinary/url-gen叠加层 API。请从这些规则中的 “导入参考” 表和 “标准叠加层代码块” 复制导入语句和模式。不要从actions/overlay导入text或image——它们来自qualifiers/source;只有source来自actions/overlay。 - ✅ 从
actions/overlay导入source;从qualifiers/source导入text和image。另外,还要导入:从qualifiers/position导入Position,从qualifiers/textStyle导入TextStyle,从qualifiers/gravity导入compass,从transformation/Transformation导入Transformation,从actions/resize导入scale。 - ✅
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 图像转换和转换参考,了解叠加层语法。
上传组件模式
- ✅ 使用组件:
import { UploadWidget } from './cloudinary/UploadWidget'
严格遵循以下初始化模式:
- ✅ 在
index.html中添加脚本(必需):
<script src="https://upload-widget.cloudinary.com/global/all.js" async></script>
- ✅ 在 useEffect 中轮询,直到
createUploadWidget可用(必需):使用setInterval(例如每 100ms)检查typeof window.cloudinary?.createUploadWidget === 'function'。只有在检查结果为true时才创建组件。准备就绪后清除间隔计时器。 - ✅ 添加超时(例如 10 秒),如果脚本始终未加载,则停止轮询并显示错误状态。在清理时和准备就绪时都要清除间隔计时器和超时计时器。
- ✅ 只创建一次组件,并将其存储在 ref 中。清理时:清除间隔计时器、清除超时计时器、移除点击监听器。
❌ 不要:只检查 window.cloudinary(函数可能尚未附加);只在 onload 中检查一次(时机不可靠);跳过 index.html,仅依赖动态注入;不设超时而无限轮询。
- ✅ 在控制面板的
settings/upload/presets中创建无签名上传预设 - ✅ 在
.env中添加:VITE_CLOUDINARY_UPLOAD_PRESET=your_preset_name - ✅ 处理回调:
<UploadWidget onUploadSuccess={(result) => { console.log('Public ID:', result.public_id); }} onUploadError={(error) => { console.error('Upload failed:', error); }} /> - ✅ 上传结果包含:
public_id、secure_url、width、height等。
有关完整的 Upload Widget 配置选项(createUploadWidget 的所有属性),请参阅官方参考文档:https://cloudinary.com/documentation/upload_widget_reference.md?install_source=skillspack&referrer=react-skill
签名上传与未签名上传(何时使用哪种)
未签名上传(更简单,无需后端):
- 适用场景:快速原型、低风险应用,或任何知道预设名称的人都可以上传的情况。
- 预设:在 Cloudinary 控制面板中创建 Unsigned 上传预设(Settings → Upload → Upload presets)。将预设名称填入
.env中的VITE_CLOUDINARY_UPLOAD_PRESET。 - 客户端:小组件只需要
cloudName和uploadPreset。无需 API 密钥或密钥;无需后端。 - 权衡:任何知道预设名称的人都可以上传。仅在可以接受这种情况时使用。
签名上传(更安全,需要后端):
- 适用场景:生产环境应用、已通过身份验证的用户,或需要控制谁可以上传的情况。
- 预设:在控制面板中创建 Signed 上传预设。后端使用 API 密钥生成签名;客户端不会接触该密钥。
- 客户端:小组件获取
api_key(来自后端)、uploadPreset,以及一个用于为每次上传调用后端的uploadSignature函数。API 密钥仅保留在服务器上。 - 权衡:需要后端(Node/Express、Next.js API route 等)为请求签名。安全性更高;签名会验证每次上传。
经验法则:除非用户明确要求“安全”或“签名”上传,否则默认使用未签名上传。不要默认使用签名上传——它需要运行中的后端,否则开箱即用时会失败。仅当用户明确要求安全/签名上传,或需要限制上传者时,才使用签名上传。
安全(签名)上传
**适用场景:**生产环境应用、已通过身份验证的用户,或需要控制谁可以上传的情况(比未签名上传更安全)。
黄金法则:
- 绝不公开或提交 API 密钥(仅限服务器端)
- 在
.gitignore中使用server/.env保存 API 密钥/密钥 - API 密钥可以发送给客户端;API 密钥必须仅保留在服务器端
快速实现:
- 将
uploadSignature用作函数(而非signatureEndpoint) - 创建小组件前从服务器获取
api_key - 在小组件配置中包含
uploadPreset - 服务器在签名参数中包含
upload_preset - 在服务器上使用 Cloudinary Node.js SDK v2
有关完整的实现模式、客户端/服务器代码示例和故障排除,请参阅 references/signed-uploads.md
视频模式
- ✅ 显示视频 → 使用 AdvancedVideo(
@cloudinary/react)。它只负责显示视频(可选应用变换)。它不是完整的播放器。 - ✅ 视频播放器 → 使用 Cloudinary Video Player(
cloudinary-video-player)。这才是真正的播放器(带样式的 UI、控件、播放列表等)。
⚠️ 重要:两种不同的方式
1. AdvancedVideo(@cloudinary/react)——用于显示视频
- 类似于
AdvancedImage的 React 组件;只负责显示应用了 Cloudinary 变换的视频 - 它不是完整的“播放器”——而是视频显示(原生 HTML5 视频,可选带控件)
- 适用场景:用户想要展示/显示视频。其用法与图片的
cld.image()类似,使用cld.video()
2. Cloudinary Video Player(cloudinary-video-player)——播放器
- 功能完整的视频播放器(带样式的 UI、控件、播放列表)。用户要求“视频播放器”时使用。
- 仅使用命令式视频元素(通过 document.createElement 创建,并附加到容器 ref);不要传入由 React 管理的
<video ref>。请参阅下文的“Cloudinary Video Player(播放器)”。
AdvancedVideo(React SDK - 用于显示视频)
- ✅ 用途:使用 Cloudinary 转换(调整大小、效果等)显示视频。它不是完整播放器,而是用于展示视频。如需播放器,请使用 Cloudinary Video Player。
- ✅ 包:
@cloudinary/react(与 AdvancedImage 相同) - ✅ 导入:
import { AdvancedVideo } from '@cloudinary/react' - ✅ 无需导入 CSS:AdvancedVideo 使用原生 HTML5 视频,无需导入 CSS
- ❌ 错误:
import '@cloudinary/react/dist/cld-video-player.css'(此路径不存在) - ✅ 创建视频实例:
const video = cld.video(publicId)(类似cld.image()) - ✅ 应用转换:像处理图片一样串联转换:
const video = cld.video('video-id') .resize(fill().width(800).height(600)) .delivery(format(auto())); - ✅ 使用组件:
<AdvancedVideo cldVid={video} controls autoplay muted /> - ✅ 文档:https://cloudinary.com/documentation/react_video_transformations.md?install_source=skillspack&referrer=react-skill
Cloudinary Video Player(播放器)
当用户需要视频播放器(带样式的 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
应该使用哪一种?
- ✅ 使用 AdvancedVideo 的情况:用户想要展示或显示视频(不需要完整播放器)。它只会显示经过转换的视频。
- ✅ 使用 Cloudinary Video Player 的情况:用户需要视频播放器——具有样式化 UI、控件和可选功能(播放列表、广告等)的实际播放器。
TypeScript 模式
TypeScript 基本用法:
- 类型导入:
import type { CloudinaryImage, CloudinaryVideo } from '@cloudinary/url-gen' - 上传结果:定义
CloudinaryUploadResult接口 - 环境变量:创建带有
ImportMetaEnv接口的vite-env.d.ts - 避免使用
any:使用适当的接口,或结合类型守卫使用unknown - Ref 类型:
useRef<HTMLVideoElement>(null),用于小部件时使用useRef<unknown>(null)
完整的 TypeScript 模式、类型守卫、ref 类型和错误解决方案,请参阅 references/typescript-patterns.md
最佳实践
- ✅ 始终使用带有自动重力的
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 元素类型
⚠️ 常见错误与解决方案
详细的错误解决方案和故障排除,请参阅 references/troubleshooting.md
快速错误参考
环境变量错误
- 环境变量:前缀错误、缺少 VITE_、未重启 → 使用正确的打包工具前缀,重启并清除缓存
- 导入:软件包或路径错误 → 使用导入参考表中的准确路径
- 上传组件:"createUploadWidget is not a function" → 使用 setInterval 轮询,不要只检查
window.cloudinary - 转换:未生效 → 正确链式调用,使用 v2 语法,并分开设置格式和质量
- 视频播放器:removeChild 错误 → 使用命令式元素(createElement),不要使用 React ref
- TypeScript:类型错误 → 请参阅 references/typescript-patterns.md
- 签名上传:签名无效 → 请参阅 references/signed-uploads.md
最常见的问题
- 环境变量未定义:清除
node_modules/.vite/,重启开发服务器,并在浏览器中强制刷新 - "createUploadWidget is not a function":使用
setInterval轮询,直到typeof window.cloudinary?.createUploadWidget === 'function' - 导入错误:仅使用导入参考表中的路径——不要猜测子路径
- 上传失败:检查预设是否存在、是否为 Unsigned(用于无签名上传),以及开发服务器是否已重启
- 视频播放器错误:仅使用命令式元素,并包含 posterOptions
- 叠加层问题:从
qualifiers/source导入text/image,不要从actions/overlay导入
如需查看包含详细解决方案的完整故障排除指南,请参阅 references/troubleshooting.md