- 对象存储
- 后端
【免费下载链接】files-sdk
Write once. Store anywhere.
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/files | authorize钩子校验登录,网关在用户前缀下生成 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/files | authorize钩子校验登录,网关在用户前缀下生成 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.
相关推荐
HSStockChart高级配置:自定义主题样式打造专属股票图表界面
HSStockChart高级配置:自定义主题样式打造专属股票图表界面 HSStockChart是一款功能强大的股票图表组件,支持K线图和分时图展示,提供手势缩放
上传文件到 Amazon S3 时究竟发生了什么——从 Bucket、Object 到一次完整的上传流程拆解
上传文件到 Amazon S3 时究竟发生了什么——从 Bucket、Object 到一次完整的上传流程拆解 本篇指南以仓库中的 what happens wh
后端文档教程Resticker完全指南:如何通过Docker容器实现自动化Restic备份
Resticker完全指南:如何通过Docker容器实现自动化Restic备份 Resticker是一款强大的Docker容器工具,能够帮助用户轻松实现Rest
运维
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考