Prompt-to-PR: 添加Cloudflare R2文件上传
将预签名R2上传集成到Next.js或Cloudflare Workers应用中的端到端标准操作程序——存储桶绑定、预签名URL和客户端上传流程。
CursorClaude CodeCodexWindsurf Next.jsCloudflareTypeScript
添加文件上传到R2,无需通过服务器代理二进制数据。代理在服务端生成预签名URL;浏览器的PUT请求直接发送到R2。
1. 需求
用户可以通过拖放界面上传文件(图片、PDF,最大10 MB)。服务器发出预签名PUT URL;浏览器直接将文件流式传输到Cloudflare R2。上传后返回一个公共读取URL。没有文件经过Next.js服务器。
2. 首次提示
Add Cloudflare R2 file upload to this Next.js 15 App Router project.
Requirements:- Use presigned PUT URLs (not proxy upload). The Next.js route only issues the presigned URL; the browser uploads directly to R2.- Install `@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner` (R2 is S3-compatible).- Create `src/app/api/upload/presign/route.ts` (POST). Accept JSON body `{ filename: string; contentType: string; size: number }`. Validate: allowed MIME types (image/jpeg, image/png, image/webp, application/pdf), max size 10 MB. Return `{ uploadUrl, publicUrl, key }`.- Read R2 credentials from env vars: R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME, NEXT_PUBLIC_R2_PUBLIC_URL.- Create `src/components/FileUpload.tsx` — a Client Component with a drag-and-drop zone. On file select: POST to /api/upload/presign, then PUT the file to the returned uploadUrl with the correct Content-Type header. Show progress, handle errors, and call an `onUpload(publicUrl)` callback.- Do not store the file in any database table; that is the caller's responsibility via the onUpload callback.3. 预期文件更改
package.json (@aws-sdk/client-s3, @aws-sdk/s3-request-presigner)src/app/api/upload/presign/route.ts (new — presign endpoint)src/components/FileUpload.tsx (new — drag-and-drop client component)src/lib/r2.ts (new — S3Client singleton).env.local.example (R2_* vars)4. 审查清单
- 预签名端点在调用R2之前验证MIME类型和大小——任何AWS调用之前拒绝不良上传。
src/lib/r2.ts中的S3Client使用endpoint: https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com和region: "auto"。- 预签名URL过期时间短(60–300秒);不是一整天。
- 来自浏览器的
PUT包含与预签名时匹配的Content-Type标头——不匹配会导致403错误。 NEXT_PUBLIC_R2_PUBLIC_URL指向存储桶的公共域名,而不是R2 API端点。FileUpload.tsx以"use client"开头——没有服务器导入。- 不仅客户端要检查大小或类型——服务器也必须进行验证。
- 键使用随机前缀(例如
crypto.randomUUID())以避免文件名冲突。
5. 测试命令
# Start dev serverbun dev
# Test presign endpoint directlycurl -X POST http://localhost:3000/api/upload/presign \ -H "Content-Type: application/json" \ -d '{"filename":"test.png","contentType":"image/png","size":12345}' | jq .
# Confirm returned uploadUrl is an R2 presigned URL (contains X-Amz-Signature)# Then PUT a real file to confirm end-to-endcurl -X PUT "<uploadUrl>" \ -H "Content-Type: image/png" \ --data-binary @test.png -v
# Fetch the public URL to verify the file is readablecurl -I "<publicUrl>"6. 常见故障
- PUT时出现403——浏览器PUT中的
Content-Type标头与预签名时使用的不匹配。确保两者使用完全相同的字符串。 NoSuchBucket——存储桶名称或账户ID错误。请在Cloudflare仪表板中仔细检查R2_BUCKET_NAME。InvalidAccessKeyId——R2 API令牌需要”对象读写”权限,而不仅仅是”读取”。- 直接PUT时出现CORS错误——R2存储桶的CORS策略必须允许来自你的源的
PUT。请在Cloudflare仪表板中的R2 → 设置 → CORS中设置。 - 代理使用
@aws-sdk/s3-presigned-post(用于POST)而不是getSignedUrl用于PUT——流程不同,客户端代码也不同。
7. 修复提示
The browser PUT to R2 returns 403 SignatureDoesNotMatch.
The Content-Type passed to getSignedUrl must exactly match the Content-Typeheader sent by the browser. Update the presign route to pass the contentTypefrom the request body into getSignedUrl, and update FileUpload.tsx to setthe Content-Type header on the PUT request to the same value.
Also confirm the S3Client endpoint is: https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.comnot the generic AWS S3 endpoint.8. PR描述
## Feature: Cloudflare R2 file upload via presigned URLs
- New POST `/api/upload/presign` validates MIME type and size, then returns a short-lived R2 presigned PUT URL- Files upload directly from the browser to R2 — zero binary data through the Next.js server- New `<FileUpload>` component: drag-and-drop, progress indicator, error state- Random UUID key prefix prevents filename collisions
**Required env vars** (see `.env.local.example`):`R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`,`R2_BUCKET_NAME`, `NEXT_PUBLIC_R2_PUBLIC_URL`
**R2 bucket setup**: enable public access and add a CORS rule allowing PUTfrom your app origin.