Storage
Cloudflare R2
Configure S3-compatible storage for uploads.
Overview
This project uses Cloudflare R2 via the S3-compatible API. Uploads are created
through a presigned URL endpoint and stored under uploads/{userId}/....
Step 1: Create an R2 bucket
- Open the Cloudflare dashboard and select R2.
- Create a new bucket (example:
shipkit-uploads). - (Optional) Bind a public domain if you want public URLs.
Step 2: Create R2 access keys
- In R2 → API Tokens, create an R2 Access Key.
- Copy the Access Key ID and Secret Access Key.
- Copy your Account ID from the R2 overview page.
Step 3: Add environment variables
Use .env.local for local development:
R2_ACCOUNT_ID=your_account_id
R2_ACCESS_KEY_ID=your_access_key_id
R2_SECRET_ACCESS_KEY=your_secret_access_key
R2_BUCKET=shipkit-uploads
R2_PUBLIC_URL=https://pub-xxxx.r2.dev
# Optional: override endpoint (defaults to https://{accountId}.r2.cloudflarestorage.com)
R2_ENDPOINT=https://your_account_id.r2.cloudflarestorage.comNotes:
R2_PUBLIC_URLis used to return a public URL after upload. If you do not expose the bucket publicly, leave it empty.- If you use a custom domain for the bucket, set it as
R2_PUBLIC_URL.
Step 4: Configure CORS for uploads
Add a CORS rule on the bucket to allow browser uploads from your site:
[
{
"AllowedOrigins": ["http://localhost:3000"],
"AllowedMethods": ["PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": [],
"MaxAgeSeconds": 3000
}
]Add your production domain when you deploy.
Step 5: Generate a presigned upload URL
The API endpoint requires a signed-in user:
curl -X POST http://localhost:3000/api/storage/presign \
-H "Content-Type: application/json" \
-H "Cookie: your_session_cookie" \
-d '{
"fileName": "logo.png",
"contentType": "image/png"
}'Response:
{
"success": true,
"data": {
"key": "uploads/{userId}/timestamp-logo.png",
"uploadUrl": "https://...signed-url...",
"publicUrl": "https://pub-xxxx.r2.dev/uploads/..."
}
}Step 6: Upload the file
Use the uploadUrl to PUT the file:
curl -X PUT "https://...signed-url..." \
-H "Content-Type: image/png" \
--data-binary "@/path/to/logo.png"How it works in this repo
- Presign endpoint:
src/app/api/storage/presign/route.ts - R2 client:
src/lib/storage/r2.ts