# @convex-dev/r2 中文文档

规范地址：https://npmcn.com/package/%40convex-dev/r2/

版本：0\.10\.4
npm 元数据更新时间：2026-10-10T00:41:22.392Z
中文译文更新时间：2026-11-10T17:42:24.334Z
软件包许可：Apache-2\.0

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40convex-dev/r2
原文对照：https://npmcn.com/package/%40convex-dev/r2/?lang=original
项目主页：https://github.com/get-convex/r2#readme
源码仓库：https://github.com/get-convex/r2

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：185 kB，73 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：6 个
- peer 依赖：vue ^3\.5\.0，react ^18 \|\| ^19，convex ^1\.25\.4，svelte \>=5\.0\.0，react-dom ^18 \|\| ^19，convex-vue ^0\.1\.5，convex-svelte ^0\.0\.11 \|\| ^0\.0\.12 \|\| ^0\.13\.0
- 首次发布：2025-01-03T23:24:29.873Z
- 最新版本发布：2026-10-10T00:41:22.203Z
- 近 30 天下载量：642502（2026-10-11 至 2026-11-09，npm 官方下载统计）
- 已发布版本数：29（近 12 个月 10 个）

## 中文简介

Convex的R2组件.

## 安装

```sh
npm install @convex-dev/r2
```

也可以使用：`pnpm add @convex-dev/r2`、`yarn add @convex-dev/r2`、`bun add @convex-dev/r2`

## README

# Convex R2 组件

[![npm version](https://badge.fury.io/js/@convex-dev%2Fr2.svg)](https://badge.fury.io/js/@convex-dev%2Fr2)

用 Cloudflare R2 存储并服务文件.

```ts
// 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);
```

**看看这个 [示例应用程序](https://npmcn.com/package/%40convex-dev/r2/example) 一个完整的例子.**

## 先决条件

### 云浮账户

-   [创建 Cloudflare 账户](https://cloudflare.com/)
-   [创建 R2 桶](https://developers.cloudflare.com/r2/buckets/create-buckets/)
-   将桶名设定为环境变量 `R2_BUCKET` 在您的凸起处 通过部署 `npx convex env set R2_BUCKET <bucket-name>`。 。 。 .
-   [增加中央资源评估政策](https://developers.cloudflare.com/r2/buckets/cors/#add-cors-policies-from-the-dashboard) 允许GET和PUT从您的 Convex 应用程序请求。 你也可以这样 使用 '\*' 来允许所有来源(谨慎使用).
    
    ```json
    [
      {
        "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 快速启动](https://docs.convex.dev/home) 来设置一个.

## 安装

安装组件包 :

```ts
npm install @convex-dev/r2
```

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

```ts
// 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 证书 :

```sh
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` 不同实例 配置 :

```ts
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/` 文件夹 :
    
    ```ts
    // 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` 在组件中钩入上传文件 :
    
    反应 :
    
    ```tsx
    // 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>
      );
    }
    ```
    
    斯维尔特:
    
    ```svelte
    <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` 举个例子.

```ts
// 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` 方法. 当您需要存储生成或下载到的文件时,此选项会有所帮助 服务器侧.

```ts
// 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组件客户端.

```ts
// 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 元素来渲染图像 :

```tsx
// 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 桶连接 - [跟着他们的文件](https://developers.cloudflare.com/rules/origin-rules/tutorials/point-to-r2-bucket-with-custom-domain/) 要开始.

Cloudflare 自动缓存最常见的文件类型 。 它们提供了完整的清单,以及关于如何为不太常见的类型进行缓存的信息 [在其文档中](https://developers.cloudflare.com/cache/concepts/default-cache-behavior/#default-cached-file-extensions)。 。 。 .

### 从对象键构建 CDN URL

```ts
// 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` 函数,它接受对象键.

```ts
// 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`数字 :

```ts
// 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);
  },
});
```

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

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

返回的文档有以下字段:

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

### 列出和介绍元数据

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

```ts
// 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` 可用于 访问新上传文件的元数据.

```ts
// 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 是什么？

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 和提交记录判断。

