mcpField
C
cloudinary-react

cloudinary-react

github.com/cloudinary-devs开发13665 次安装6 周前更新
代码仓库

关于 cloudinary-react

cloudinary-react 是面向 React 开发者的 Cloudinary 技能,提供图片、视频和上传功能的实现模式与常见错误排查。内容涵盖不同打包工具的环境变量配置、上传组件、转换及视频播放器。

主要功能

  1. 01配置可复用的 Cloudinary 实例
  2. 02接入 Upload Widget
  3. 03实现签名与无签名上传
  4. 04应用图片和视频转换
  5. 05排查环境变量及播放器问题

适用场景

  1. 01在 React 应用中显示转换后的图片
  2. 02为应用添加 Cloudinary 上传组件
  3. 03排查上传组件或视频播放器故障

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cloudinary-devs/skills --skill cloudinary-react

安装后在 Claude Code 中输入/cloudinary-react确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
signed-uploads.md
troubleshooting.md
typescript-patterns.md
video-player.md

详细介绍

Cloudinary React 技能

适用场景

  • 用户在 React 应用(Vite、Create React App、Parcel 等)中构建或调试 Cloudinary 时。
  • 实现或修复以下功能时:Upload Widget、AdvancedImage/AdvancedVideo、转换、叠加层、图片库、视频播放器,或签名/非签名上传。
  • 用户遇到诸如“createUploadWidget is not a function”、从 @cloudinary/url-gen 导入错误、上传预设问题或视频播放器 DOM 错误等问题时。

快速开始

最常见的操作:

  1. 设置:创建包含 cld 实例的配置文件(参见“项目设置”部分)
  2. 显示图片:const img = cld.image('id').resize(fill().width(800)); <AdvancedImage cldImg={img} />
  3. Upload Widget:在 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

说明

在 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 文件。

关键参考资料

**@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. 上传组件(未签名,从头开始)

严格遵循以下模式:

  1. 在 index.html 中添加脚本(必需):在 index.html 中添加 <script src="https://upload-widget.cloudinary.com/global/all.js" async></script>。不要只依赖从 React 动态注入脚本——这种方式不可靠。
  2. 在 useEffect 中轮询(必需):在 useEffect 中使用 setInterval 轮询(例如每 100ms 一次),直到 typeof window.cloudinary?.createUploadWidget === 'function'。只有此时才能创建组件。仅检查一次(即使在 onload 中检查)也不可靠,因为 window.cloudinary 可能先于 createUploadWidget 挂载而存在。
  3. 添加超时:设置超时时间(例如 10 秒),如果脚本始终未加载,则停止轮询并显示错误。清理时清除 interval 和 timeout。
  4. 只创建一次组件:当 createUploadWidget 可用时,创建组件并将其存储在 ref 中。清除 interval 和 timeout。传入以下选项:{ 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. 视频播放器

  • 只使用命令式视频元素(使用 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:
    1. 清除 Vite 缓存:rm -rf node_modules/.vite/(或删除 node_modules/.vite 文件夹)
    2. 重启开发服务器:npm run dev
    3. 强制刷新浏览器:Cmd+Shift+R(Mac)或 Ctrl+Shift+F5(Windows/Linux)
    4. 如果仍为空,请使用静态配置作为临时解决方案(创建 cloudinaryConfig.ts,在开发时写入硬编码的云名称,之后再切换回环境变量),并参阅常见错误 →“VITE_ 前缀必需,否则环境变量为 undefined”

其他打包工具(非 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 密钥/密钥)
  • 未签名上传失败时:首先检查用户是否已配置上传预设:
    1. .env 中是否设置了 VITE_CLOUDINARY_UPLOAD_PRESET?(必须与预设名称完全一致)
    2. 控制台的 Settings → Upload → Upload presets 下是否存在该预设?
    3. 该预设是否设为Unsigned(而非 Signed)?
    4. 添加或更新 .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()]}
  • ✅ 建议的插件顺序:
    1. responsive() - 第一(处理断点)
    2. placeholder() - 第二(加载时显示占位图)
    3. lazyload() - 第三(进入视口后才延迟加载)
    4. 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'

严格遵循以下初始化模式:

  1. ✅ 在 index.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
  • ✅ 处理回调:
    <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 等)为请求签名。安全性更高;签名会验证每次上传。

经验法则:除非用户明确要求“安全”或“签名”上传,否则默认使用未签名上传。不要默认使用签名上传——它需要运行中的后端,否则开箱即用时会失败。仅当用户明确要求安全/签名上传,或需要限制上传者时,才使用签名上传。

安全(签名)上传

**适用场景:**生产环境应用、已通过身份验证的用户,或需要控制谁可以上传的情况(比未签名上传更安全)。

黄金法则:

  1. 绝不公开或提交 API 密钥(仅限服务器端)
  2. 在 .gitignore 中使用 server/.env 保存 API 密钥/密钥
  3. 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

最常见的问题

  1. 环境变量未定义:清除 node_modules/.vite/,重启开发服务器,并在浏览器中强制刷新
  2. "createUploadWidget is not a function":使用 setInterval 轮询,直到 typeof window.cloudinary?.createUploadWidget === 'function'
  3. 导入错误:仅使用导入参考表中的路径——不要猜测子路径
  4. 上传失败:检查预设是否存在、是否为 Unsigned(用于无签名上传),以及开发服务器是否已重启
  5. 视频播放器错误:仅使用命令式元素,并包含 posterOptions
  6. 叠加层问题:从 qualifiers/source 导入 text/image,不要从 actions/overlay 导入

如需查看包含详细解决方案的完整故障排除指南,请参阅 references/troubleshooting.md

评论

支持 Markdown