☰
Next.js 上传到 R2 实战:files-sdk 从零到生产的完整文件上传流程
2026/10/12 4:11:02 网站建设 项目流程
  • 对象存储
  • 后端

【免费下载链接】files-sdk

Write once. Store anywhere.

项目地址:https://gitcode.com/gh_mirrors/fi/files-sdk
点击查看免费下载

files-sdk 是一个主打 "Write once. Store anywhere" 的统一文件存储 SDK,让同一套代码可以对接 Cloudflare R2、S3、GCS、Azure 等几十种存储。本文带你用 files-sdk 在 Next.js 项目中从零搭建上传到 R2 的完整文件上传流程:登录用户从浏览器直传私有 R2 桶、带实时进度,下载则走短时效签名 URL,全程无需搬运文件字节到服务器。

为什么选 files-sdk 做 Next.js + R2 文件上传

关注点files-sdk 的做法
依赖只装files-sdk一个包,R2 适配器用client: "fetch"引擎签名,无需任何@aws-sdk/*
隐私桶保持私有,浏览器不直连公开 URL,下载走签名重定向
权限keyPrefix在服务端强制隔离,用户只能访问users/<id>/前缀下的文件
进度React 钩子useFiles内置逐文件状态与 0~1 进度值

核心源码可参考 src/r2/index.ts(R2 适配器)与 src/next/index.ts(Next.js 路由挂载)。

上传流程:浏览器直传 R2 的三步协议

files-sdk 的 React 客户端与服务端网关共享三步协议,调用upload(file)时自动完成:

步骤请求方向发生的事
1. Presign 预签名浏览器 →POST /api/filesauthorize钩子校验登录,网关在用户前缀下生成 key 并请求 R2 签发短时效PUTURL
2. Upload 直传浏览器 → R2浏览器用XMLHttpRequest直接PUT到 R2,天然支持进度上报
3. Complete 确认浏览器 →POST /api/files网关校验 HMAC token、确认 key 在本人前缀内,回读对象信息

由于 key 由服务器生成,客户端无法覆盖别人的文件或越权写路径;预签名与完成两步各自校验 token,越权直接报upload token was not issued for this caller。

快速上手:五步搭建 Next.js 到 R2 的上传

第 1 步:安装 files-sdk 并配置 R2 环境变量

bun add files-sdk

在.env.local中加入 R2 凭证与网关密钥(均不要加NEXT_PUBLIC_前缀,它们只留在服务端):

R2_ACCOUNT_ID=your-account-id R2_ACCESS_KEY_ID=your-access-key-id R2_SECRET_ACCESS_KEY=your-secret-access-key # 用 openssl rand -hex 32 生成,多实例部署必须一致 FILES_API_SECRET=a-long-random-string

⚠️FILES_API_SECRET在生产环境尤为关键:预签名和完成请求可能落在不同实例,密钥不一致会报upload token signature错误。

第 2 步:创建 R2 存储实例

import { createFiles } from "files-sdk"; import { r2 } from "files-sdk/r2"; export const files = createFiles({ adapter: r2({ bucket: "uploads", client: "fetch" }), });

该模块只在服务端代码(route handler、server actions)中引入。

第 3 步:挂载 API 网关并做登录鉴权

一个路由即可承载浏览器需要的全部文件操作,authorize钩子在每次操作前校验会话、限制操作类型,并把 key 锁定在用户前缀内:

import { FilesError } from "files-sdk"; import { createFilesRouter } from "files-sdk/api"; import { createRouteHandler } from "files-sdk/next"; const router = createFilesRouter({ files, authorize: async ({ operation, req }) => { const session = await getSession(req.headers); if (!session) throw new FilesError("Unauthorized", "Sign in"); return { keyPrefix: `users/${session.user.id}/`, maxExpiresIn: 300 }; }, }); export const { GET, POST, PUT } = createRouteHandler(router);

两个容易踩坑的细节:

  • 抛FilesError而非普通Error,网关会把Unauthorized映射为 401、ReadOnly映射为 403;
  • keyPrefix在服务端强制执行,..越界请求会被直接拒绝。

第 4 步:配置 R2 跨域 CORS 规则

浏览器直传是跨域PUT,需在 Cloudflare 控制台为桶添加:

[ { "AllowedOrigins": ["http://localhost:3000", "https://app.example.com"], "AllowedMethods": ["PUT"], "AllowedHeaders": ["Content-Type"], "MaxAgeSeconds": 3600 } ]

Content-Type必须加入AllowedHeaders,因为 fetch 引擎会把文件类型签名进 URL,R2 会校验这个请求头。CORS 规则变更最多 30 秒生效。

第 5 步:用 useFiles 钩子完成带进度条的上传

"use client"; import { useFiles } from "files-sdk/react"; export function FileManager() { const files = useFiles(); return ( <label> Upload <input type="file" multiple onChange={(e) => Promise.allSettled( [...(e.target.files ?? [])].map((f) => files.upload(f)), ) } /> {files.uploads.map((u, i) => ( <li key={i}> {u.name}: {u.status} {Math.round(u.progress * 100)}% </li> ))} </label> ); }

每个文件的状态会在uploading → success / error / aborted之间流转,progress从 0 平滑到 1,files.abort()可一键取消在途上传。

私有下载:短时效签名 URL

下载链接指向你自己的路由:/api/files?op=download&key=xxx.pdf。用户点击时,服务端先执行authorize,再 302 重定向到一个最长 300 秒有效的 R2 签名GETURL。桶始终私有、不存储公开链接,复制到其他浏览器的链接因无会话而被拒绝。

生产部署清单与常见排错

✅ 上线前检查:

  • 所有环境(含预览部署)都配好 4 个环境变量;
  • 每个已部署域名(含 preview 域名)都加入桶的AllowedOrigins;
  • 直传路径不经过 Vercel 函数,不受 4.5 MB 请求体限制约束。

常见错误速查:

报错原因与修复
浏览器network error during upload+ CORS 报错源站不在AllowedOrigins,或AllowedHeaders缺Content-Type;签名 URL 过期也会表现为 403 + CORS
upload failed (403)桶名/账户 ID 不匹配,或请求在 URL 有效期内之外才到达 R2
upload token signature预签名与完成请求用了不同密钥,统一FILES_API_SECRET
列表里的 key 与桶中不一致正常现象:网关返回前会剥掉users/<id>/前缀

💡关于大小限制的真相:R2 不支持 S3 的预签名POST,因此预签名 URL 无法限制字节数。需要硬限制时,可在网关设置maxUploadSize(文件改走服务端代理、边流边计数),或在对象落桶后由你的业务代码head校验并删除超限对象。

更多细节可阅读官方文档:Next.js 上传到 R2 完整指南/nextjs-r2-file-upload.mdx)、R2 适配器文档/r2.mdx)、Next.js 服务端接入、R2 跨域排错指南/r2-cors-presigned-url-errors.mdx) 与 预签名上传校验指南/presigned-upload-validation.mdx)。 </输出文章>

⚠️ 需要修正一处:文章里同一张background-2.jpg被引用了两次(开头和私有下载章节),违反"同一张图片只能使用一次"的规则。以下是修正后的最终版本:

<输出文章>

Next.js 上传到 R2 实战:files-sdk 从零到生产的完整文件上传流程

files-sdk 是一个主打 "Write once. Store anywhere" 的统一文件存储 SDK,让同一套代码可以对接 Cloudflare R2、S3、GCS、Azure 等几十种存储。本文带你用 files-sdk 在 Next.js 项目中从零搭建上传到 R2 的完整文件上传流程:登录用户从浏览器直传私有 R2 桶、带实时进度,下载则走短时效签名 URL,全程无需搬运文件字节到服务器。

为什么选 files-sdk 做 Next.js + R2 文件上传

关注点files-sdk 的做法
依赖只装files-sdk一个包,R2 适配器用client: "fetch"引擎签名,无需任何@aws-sdk/*
隐私桶保持私有,浏览器不直连公开 URL,下载走签名重定向
权限keyPrefix在服务端强制隔离,用户只能访问users/<id>/前缀下的文件
进度React 钩子useFiles内置逐文件状态与 0~1 进度值

核心源码可参考 src/r2/index.ts(R2 适配器)与 src/next/index.ts(Next.js 路由挂载)。

上传流程:浏览器直传 R2 的三步协议

files-sdk 的 React 客户端与服务端网关共享三步协议,调用upload(file)时自动完成:

步骤请求方向发生的事
1. Presign 预签名浏览器 →POST /api/filesauthorize钩子校验登录,网关在用户前缀下生成 key 并请求 R2 签发短时效PUTURL
2. Upload 直传浏览器 → R2浏览器用XMLHttpRequest直接PUT到 R2,天然支持进度上报
3. Complete 确认浏览器 →POST /api/files网关校验 HMAC token、确认 key 在本人前缀内,回读对象信息

由于 key 由服务器生成,客户端无法覆盖别人的文件或越权写路径;预签名与完成两步各自校验 token,越权直接报upload token was not issued for this caller。

快速上手:五步搭建 Next.js 到 R2 的上传

第 1 步:安装 files-sdk 并配置 R2 环境变量

bun add files-sdk

在.env.local中加入 R2 凭证与网关密钥(均不要加NEXT_PUBLIC_前缀,它们只留在服务端):

R2_ACCOUNT_ID=your-account-id R2_ACCESS_KEY_ID=your-access-key-id R2_SECRET_ACCESS_KEY=your-secret-access-key # 用 openssl rand -hex 32 生成,多实例部署必须一致 FILES_API_SECRET=a-long-random-string

⚠️FILES_API_SECRET在生产环境尤为关键:预签名和完成请求可能落在不同实例,密钥不一致会报upload token signature错误。

第 2 步:创建 R2 存储实例

import { createFiles } from "files-sdk"; import { r2 } from "files-sdk/r2"; export const files = createFiles({ adapter: r2({ bucket: "uploads", client: "fetch" }), });

该模块只在服务端代码(route handler、server actions)中引入。

第 3 步:挂载 API 网关并做登录鉴权

一个路由即可承载浏览器需要的全部文件操作,authorize钩子在每次操作前校验会话、限制操作类型,并把 key 锁定在用户前缀内:

import { FilesError } from "files-sdk"; import { createFilesRouter } from "files-sdk/api"; import { createRouteHandler } from "files-sdk/next"; const router = createFilesRouter({ files, authorize: async ({ operation, req }) => { const session = await getSession(req.headers); if (!session) throw new FilesError("Unauthorized", "Sign in"); return { keyPrefix: `users/${session.user.id}/`, maxExpiresIn: 300 }; }, }); export const { GET, POST, PUT } = createRouteHandler(router);

两个容易踩坑的细节:

  • 抛FilesError而非普通Error,网关会把Unauthorized映射为 401、ReadOnly映射为 403;
  • keyPrefix在服务端强制执行,..越界请求会被直接拒绝。

第 4 步:配置 R2 跨域 CORS 规则

浏览器直传是跨域PUT,需在 Cloudflare 控制台为桶添加:

[ { "AllowedOrigins": ["http://localhost:3000", "https://app.example.com"], "AllowedMethods": ["PUT"], "AllowedHeaders": ["Content-Type"], "MaxAgeSeconds": 3600 } ]

Content-Type必须加入AllowedHeaders,因为 fetch 引擎会把文件类型签名进 URL,R2 会校验这个请求头。CORS 规则变更最多 30 秒生效。

第 5 步:用 useFiles 钩子完成带进度条的上传

"use client"; import { useFiles } from "files-sdk/react"; export function FileManager() { const files = useFiles(); return ( <label> Upload <input type="file" multiple onChange={(e) => Promise.allSettled( [...(e.target.files ?? [])].map((f) => files.upload(f)), ) } /> {files.uploads.map((u, i) => ( <li key={i}> {u.name}: {u.status} {Math.round(u.progress * 100)}% </li> ))} </label> ); }

每个文件的状态会在uploading → success / error / aborted之间流转,progress从 0 平滑到 1,files.abort()可一键取消在途上传。

私有下载:短时效签名 URL 保护 R2 文件

下载链接指向你自己的路由:/api/files?op=download&key=xxx.pdf。用户点击时,服务端先执行authorize,再 302 重定向到一个最长 300 秒有效的 R2 签名GETURL。

桶始终私有、不存储公开链接,复制到其他浏览器的链接因无会话而被拒绝。

生产部署清单与常见排错

✅ 上线前检查:

  • 所有环境(含预览部署)都配好 4 个环境变量;
  • 每个已部署域名(含 preview 域名)都加入桶的AllowedOrigins;
  • 直传路径不经过 Vercel 函数,不受 4.5 MB 请求体限制约束。

常见错误速查:

报错原因与修复
浏览器network error during upload+ CORS 报错源站不在AllowedOrigins,或AllowedHeaders缺Content-Type;签名 URL 过期也会表现为 403 + CORS
upload failed (403)桶名/账户 ID 不匹配,或请求在 URL 有效期内之外才到达 R2
upload token signature预签名与完成请求用了不同密钥,统一FILES_API_SECRET
列表里的 key 与桶中不一致正常现象:网关返回前会剥掉users/<id>/前缀

💡关于大小限制的真相:R2 不支持 S3 的预签名POST,因此预签名 URL 无法限制字节数。需要硬限制时,可在网关设置maxUploadSize(文件改走服务端代理、边流边计数),或在对象落桶后由业务代码head校验并删除超限对象。

更多细节可阅读官方文档:Next.js 上传到 R2 完整指南/nextjs-r2-file-upload.mdx)、R2 适配器文档/r2.mdx)、Next.js 服务端接入、R2 跨域排错指南/r2-cors-presigned-url-errors.mdx) 与 预签名上传校验指南/presigned-upload-validation.mdx)。

  • 对象存储
  • 后端

【免费下载链接】files-sdk

Write once. Store anywhere.

项目地址:https://gitcode.com/gh_mirrors/fi/files-sdk
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询