@convex-dev/r2

中文文档
0.10.4发布于 2026/10/10Apache-2.0TypeScriptES Module近 30 天下载 64.3万

Convex的R2组件.

中文使用文档
文档目录17 节
  1. 先决条件
  2. 云浮账户
  3. Convex 应用程序
  4. 安装
  5. 使用多桶
  6. 上传文件
  7. 使用自定义对象密钥
  8. 从动作存储文件
  9. 服务文件
  10. 在查询中生成文件 URL
  11. 从 Cloudflare 的 CDN 通过自定义域服务文件
  12. 设置 R2 自定义域
  13. 从对象键构建 CDN URL
  14. 删除文件
  15. 访问文件元数据
  16. 列出和介绍元数据
  17. 上传后访问元数据

Convex R2 组件

npm version

用 Cloudflare R2 存储并服务文件.

// or @convex-dev/r2/svelte for Svelte!
import { useUploadFile } from "@convex-dev/r2/react";

// Upload files from React
const uploadFile = useUploadFile(api.example);
// ...in a callback
const key = await uploadFile(file);

// Access files on the server
const url = await r2.getUrl(key);
const response = await fetch(url);

看看这个 示例应用程序 一个完整的例子.

先决条件

云浮账户

  • 创建 Cloudflare 账户
  • 创建 R2 桶
  • 将桶名设定为环境变量 R2_BUCKET 在您的凸起处 通过部署 npx convex env set R2_BUCKET <bucket-name>。 。 。 .
  • 增加中央资源评估政策 允许GET和PUT从您的 Convex 应用程序请求。 你也可以这样 使用 '*' 来允许所有来源(谨慎使用).
    [
      {
        "AllowedOrigins": ["http://localhost:5173"],
        "AllowedMethods": ["GET", "PUT"],
        "AllowedHeaders": ["Content-Type"]
      }
    ]
    
  • 创建 API 令牌
    • 在您 Cloudflare 仪表板中的主 R2 页面上,单击 管理 R2 API 键
    • 点击 创建 API 脚本
    • 编辑符号名称
    • 设置权限到 对象读取并写入
    • 下 指定桶,选择您在上面创建的桶
    • 可选更改 TTL
    • 点击 创建 API 脚本
  • 在下一个屏幕上,你会得到你以后需要的四个值:
    • 调值数字 : R2_TOKEN
    • 访问密钥 ID数字 : R2_ACCESS_KEY_ID
    • 秘密访问密钥数字 : R2_SECRET_ACCESS_KEY
    • 终点数字 : R2_ENDPOINT

Convex 应用程序

你需要一个Convex App来使用组件. 跟着任何 Convex 快速启动 来设置一个.

安装

安装组件包 :

npm install @convex-dev/r2

创建一个 convex.config.ts 应用程序中的文件 convex/ 文件夹并安装 通过调用 use数字 :

// convex/convex.config.ts
import { defineApp } from "convex/server";
import r2 from "@convex-dev/r2/convex.config.js";

const app = defineApp();
app.use(r2);

export default app;

使用您先前记录的值设置您的 API 证书 :

npx convex env set R2_TOKEN xxxxx
npx convex env set R2_ACCESS_KEY_ID xxxxx
npx convex env set R2_SECRET_ACCESS_KEY xxxxx
npx convex env set R2_ENDPOINT xxxxx
npx convex env set R2_BUCKET xxxxx

注:环境变量只有在不直接通过配置时才需要 页:1 R2 建筑师。 您也可以通过单个值来覆盖 具体的环境变量.

使用多桶

要使用多个 R2 桶, 请另建 R2 不同实例 配置 :

import { R2 } from "@convex-dev/r2";
import { components } from "./_generated/api";

// Primary bucket — reads from R2_* environment variables
const primary = new R2(components.r2);

// Archive bucket — configured explicitly
const archive = new R2(components.r2, {
  bucket: process.env.ARCHIVE_BUCKET!,
  endpoint: process.env.ARCHIVE_ENDPOINT!,
  accessKeyId: process.env.ARCHIVE_ACCESS_KEY_ID!,
  secretAccessKey: process.env.ARCHIVE_SECRET_ACCESS_KEY!,
});

每个实例都维持自己的配置和S3客户端. 全部元数据 桶并存于同名Convex表,由桶名索引.

上传文件

文件上传到R2通常使用签名的urls. R2组件提供钩子 对处理整个上传进程的React和Svelte:

  • 生成已签名的 url
  • 上传文件到 R2
  • 在您的 Convex 数据库中存储文件的元数据

重要: 对于在召回中打入的正确类型脚本,请通过 DataModel 键入为通用参数 r2.clientApi<DataModel>()。这将确保 ctx 参数与您的数据库计划一起输入并返回查询 Doc<"tableName"> 改为 GenericDocument。 。 。 .

  1. 在您的应用程序中的文件里提供 R2 组件客户端的证明 convex/ 文件夹 :

    // convex/example.ts
    import { R2 } from "@convex-dev/r2";
    import { components } from "./_generated/api";
    import type { DataModel } from "./_generated/dataModel";
    
    export const r2 = new R2(components.r2);
    
    // Pass DataModel as a generic type parameter to get proper TypeScript typing
    // for all callback contexts. Without this, ctx will be typed as GenericDocument
    // instead of your specific table types.
    export const { generateUploadUrl, syncMetadata } = r2.clientApi<DataModel>({
      checkUpload: async (ctx, bucket) => {
        // const user = await userFromAuth(ctx);
        // ...validate that the user can upload to this bucket
      },
      onUpload: async (ctx, bucket, key) => {
        // ...do something with the key
        // This technically runs in the `syncMetadata` mutation, as the upload
        // is performed from the client side. Will run if using the `useUploadFile`
        // hook, or if `syncMetadata` function is called directly. Runs after the
        // `checkUpload` callback.
      },
    });
    
  2. 使用 useUploadFile 在组件中钩入上传文件 :

    反应 :

    // src/App.tsx
    import { FormEvent, useRef, useState } from "react";
    import { useAction } from "convex/react";
    import { api } from "../convex/_generated/api";
    import { useUploadFile } from "@convex-dev/r2/react";
    
    export default function App() {
      // Passing the entire api exported from `convex/example.ts` to the hook.
      // This must include `generateUploadUrl` and `syncMetadata` from the r2 client api.
      const uploadFile = useUploadFile(api.example);
      const imageInput = useRef<HTMLInputElement>(null);
      const [selectedImage, setSelectedImage] = useState<File | null>(null);
    
      async function handleUpload(event: FormEvent) {
        event.preventDefault();
    
        // The file is uploaded to R2, metadata is synced to the database, and the
        // key of the newly created object is returned.
        await uploadFile(selectedImage!);
        setSelectedImage(null);
        imageInput.current!.value = "";
      }
      return (
        <form onSubmit={handleUpload}>
          <input
            type="file"
            accept="image/*"
            ref={imageInput}
            onChange={(event) => setSelectedImage(event.target.files![0])}
            disabled={selectedImage !== null}
          />
          <input
            type="submit"
            value="Upload"
            disabled={selectedImage === null}
          />
        </form>
      );
    }
    

    斯维尔特:

    <script lang="ts">
       import { useUploadFile } from "@convex-dev/r2/svelte";
       import { api } from "../convex/_generated/api";
    
       const uploadFile = useUploadFile(api.example);
    
       let selectedImage = $state<File | null>(null);
    
       async function handleUpload(file: File) {
         await uploadFile(file);
         selectedImage = null;
       }
     </script>
    
     <form
       onsubmit={() => {
         if (selectedImage) handleUpload(selectedImage);
       }}
     >
       <input
         type="file"
         accept="image/*"
         onchange={(e) => {
           selectedImage = e.currentTarget.files?.[0] ?? null;
         }}
         disabled={selectedImage !== null}
       />
       <button type="submit" disabled={selectedImage === null}> Upload </button>
     </form>
    

使用自定义对象密钥

这个 r2.generateUploadUrl 函数生成一个 uuid 以用作对象键 默认,但如果需要,可以提供自定义密钥。 说明: generateUploadUrl 函数返回 r2.clientApi 不接受习惯 键,因为此函数是一个从客户端调用到您的突变 不希望您的客户定义您的对象密钥 。 提供自定义密钥 需要自己做个变异 叫做 generateUploadUrl 方法 页:1 r2 举个例子.

// convex/example.ts
import { R2 } from "@convex-dev/r2";
import { components } from "./_generated/api";

export const r2 = new R2(components.r2);

// A custom mutation that creates a key from the user id and a uuid. If the key
// already exists, the mutation will fail.
export const generateUploadUrlWithCustomKey = mutation({
  args: {},
  handler: async (ctx) => {
    // Replace this with whatever function you use to get the current user
    const currentUser = await getUser(ctx);
    if (!currentUser) {
      throw new Error("User not found");
    }
    const key = `${currentUser.id}.${crypto.randomUUID()}`;
    return r2.generateUploadUrl(key);
  },
});

从动作存储文件

文件可以直接从使用此选项的动作中存储在 R2 中 r2.store 方法. 当您需要存储生成或下载到的文件时,此选项会有所帮助 服务器侧.

// convex/example.ts
import { internalAction } from "./_generated/server";
import { R2 } from "@convex-dev/r2";

const r2 = new R2(components.r2);

export const store = internalAction({
  handler: async (ctx) => {
    // Download a random image from picsum.photos
    const url = "https://picsum.photos/200/300";
    const response = await fetch(url);
    const blob = await response.blob();

    // This function call is the only required part, it uploads the blob to R2,
    // syncs the metadata, and returns the key. The key is a uuid by default, but
    // an optional custom key can be provided in the options object. A MIME type
    // can also be provided, which will override the type inferred for blobs. A
    // Content-Disposition and Cache-Control header can also be provided if
    // necessary.
    const key = await r2.store(ctx, blob, {
      key: "my-custom-key",
      type: "image/jpeg",
      disposition: "attachment; filename=example.jpg",
      cacheControl: "max-age=3600",
    });

    // Example use case, associate the key with a record in your database
    await ctx.runMutation(internal.example.insertImage, { key });
  },
});

这个 store 方法 :

  • 需要一个 Blob, (中文(简体) ), Buffer,或 Uint8Array 并存储在R2中
  • 将元数据同步到您的 Convex 数据库
  • 返回可用于稍后访问文件的密钥

服务文件

存储在 R2 中的文件可以通过生成一个 URL 指向一个 给定文件.

在查询中生成文件 URL

服务文件的最简单方式是返回 URL 和其他需要的数据 根据你的应用程序 从询问和突变.

文件 URL 可以由对象键生成 r2.getUrl 函数 R2组件客户端.

// convex/listMessages.ts
import { components } from "./_generated/api";
import { query } from "./_generated/server";
import { R2 } from "@convex-dev/r2";

const r2 = new R2(components.r2);

export const list = query({
  args: {},
  handler: async (ctx) => {
    // In this example, messages have an imageKey field with the object key
    const messages = await ctx.db.query("messages").collect();
    return Promise.all(
      messages.map(async (message) => ({
        ...message,
        imageUrl: await r2.getUrl(
          message.imageKey,
          // Options object is optional, can be omitted
          {
            // Custom expiration time in seconds, default is 900 (15 minutes)
            expiresIn: 60 * 60 * 24, // 1 day
          },
        ),
      })),
    );
  },
});

文件 URL 可以用于 img 元素来渲染图像 :

// src/App.tsx
function Image({ message }: { message: { url: string } }) {
  return <img src={message.url} height="300px" width="auto" />;
}

从 Cloudflare 的 CDN 通过自定义域服务文件

r2.getUrl() 生成已签名的过期的 S3 URL( 默认为 15分钟) 和 路由直接通向R2的S3 API端点,绕过Cloudflare的边缘缓存. 对于 使用您需要永久的 CDN 标记的 URL( 图片、 相机、 相机、, 您可以将自定义域名连接到您的 R2 桶并构建 从对象键直接取出URL(如cdn.example.com,其中example.com是您拥有的一个域).

设置 R2 自定义域

您首先需要将自定义域与您的 R2 桶连接 - 跟着他们的文件 要开始.

Cloudflare 自动缓存最常见的文件类型 。 它们提供了完整的清单,以及关于如何为不太常见的类型进行缓存的信息 在其文档中。 。 。 .

从对象键构建 CDN URL

// convex/files.ts
import { R2 } from "@convex-dev/r2";
import { components } from "./_generated/api";
import { query } from "./_generated/server";

const r2 = new R2(components.r2);

// Build CDN URL from a stored object key.
function getCdnUrl(key: string): string {
  // Replace with your domain
  const base = "cdn.example.com"
  return `${base}/${key.split('/').map(encodeURIComponent).join('/')`
}

export const listMessages = query({
  args: {},
  handler: async (ctx) => {
    const messages = await ctx.db.query("messages").collect();
    return messages.map((message) => ({
      ...message,
      // Return cached, CDN served image
      imageUrl: getCdnUrl(message.imageKey),
    }));
  },
});

说明: 别用那个 r2.dev 生产中的公共开发 URL - 它的速率有限且没有被标记. 使用自定义域代替 .

删除文件

存储在 R2 中的文件可以通过 r2.deleteObject 函数,它接受对象键.

// convex/images.ts
import { v } from "convex/values";
import { mutation } from "./_generated/server";
import { R2 } from "@convex-dev/r2";

const r2 = new R2(components.r2);

export const deleteObject = mutation({
  args: {
    key: v.string(),
  },
  handler: async (ctx, args) => {
    return await r2.deleteObject(ctx, args.key);
  },
});

访问文件元数据

R2 文件的文件元数据可以从动作中访问 r2.getMetadata数字 :

// convex/images.ts
import { v } from "convex/values";
import { query } from "./_generated/server";
import { R2 } from "@convex-dev/r2";

const r2 = new R2(components.r2);

export const getMetadata = query({
  args: {
    key: v.string(),
  },
  handler: async (ctx, args) => {
    return await r2.getMetadata(args.key);
  },
});

这是返回的文档的一个例子:

{
  "ContentType": "image/jpeg",
  "ContentLength": 125338,
  "LastModified": "2024-03-20T12:34:56Z"
}

返回的文档有以下字段:

  • ContentType: 如果上传时提供了文件的内容类型
  • ContentLength: 以字节表示的文件大小
  • LastModified: 文件的最后修改日期

列出和介绍元数据

元数据可通过下列方式从动作中列出或标出: r2.listMetadata 财务报告和审定财务报表 r2.pageMetadata。 。 。 .

// convex/example.ts
import { query } from "./_generated/server";
import { R2 } from "@convex-dev/r2";

const r2 = new R2(components.r2);

export const list = query({
  args: {
    limit: v.optional(v.number()),
  },
  handler: async (ctx, args) => {
    return r2.listMetadata(ctx, args.limit);
  },
});

export const page = query({
  args: {
    paginationOpts: paginationOptsValidator,
  },
  handler: async (ctx, args) => {
    return r2.pageMetadata(ctx, args.paginationOpts);
  },
});

上传后访问元数据

这个 onSyncMetadata 可使用回调来运行每个元数据后的突变 同步 。 这个 useUploadFile 钩钩每次上传后都会同步元数据, 所以此 函数也会每次运行.

因为这是在元数据同步后运行的 r2.getMetadata 可用于 访问新上传文件的元数据.

// convex/example.ts
import { R2, type R2Callbacks } from "@convex-dev/r2";
import { components } from "./_generated/api";
import type { DataModel } from "./_generated/dataModel";

export const r2 = new R2(components.r2);

const callbacks: R2Callbacks = internal.example;

export const { generateUploadUrl, syncMetadata, onSyncMetadata } = r2.clientApi<DataModel>(
  {
    // Pass the functions from this file back into the component.
    // Technically only an object with `onSyncMetadata` is required, the recommended
    // pattern is just for convenience.
    callbacks,

    onSyncMetadata: async (ctx, args) => {
      // args: { bucket: string; key: string; isNew: boolean }
      // args.isNew is true if the key did not previously exist in your Convex R2
      // metadata table
      const metadata = await r2.getMetadata(ctx, args.key);
      // log metadata of synced object
      console.log("metadata", metadata);
    },
  },
);

版本与发布记录

@convex-dev/r2 共发布 29 个版本,首次发布于 2025/01/04;过去 12 个月发布了 10 个版本。

版本发布日期距今
0.10.4latest2026/10/101 个月前
0.10.32026/10/071 个月前
0.10.22026/06/164 个月前
0.10.12026/04/236 个月前
0.10.02026/04/176 个月前
0.9.22026/04/146 个月前

关于 @convex-dev/r2 的常见问题

@convex-dev/r2 是什么?

Convex的R2组件.

如何安装 @convex-dev/r2?

在项目目录运行 npm install @convex-dev/r2,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:convex(^1.25.4)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@convex-dev/r2 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@convex-dev/r2 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@convex-dev/r2 可以商用吗?

@convex-dev/r2 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@convex-dev/r2 的下载量有多少?

根据 npm 官方下载统计,@convex-dev/r2 在 2026/10/11 – 2026/11/09 这 30 天内被下载 642,502 次,日均约 21,417 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@convex-dev/r2 还在维护吗?

最新版本 0.10.4 发布于 2026/10/10(1 个月前);至今共发布 29 个版本,其中过去 12 个月发布了 10 个;首次发布于 2025/01/04。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。