Cloudflare R2 Image Storage
Overview
Migrated all image storage from Firebase Storage to Cloudflare R2. Includes image optimization, database URL updates, and a server-side upload endpoint.
Storage Details
- Provider: Cloudflare R2
- Bucket: ricoya-images
- Region: Asia Pacific
- Storage Class: Standard
- Free Tier: 10GB storage, 10 million reads/month
Image Inventory
| Directory | Files | Purpose |
|---|---|---|
| menu-items/ | 304 | Food item photos |
| restaurants/ | 20 | Restaurant logos and banners |
| uploads/ | 24 | Delivery proofs, miscellaneous |
| Total | 348 |
Optimization
All images processed before upload:
- Images wider than 1200px resized to 1200px width (maintaining aspect ratio)
- JPEG quality set to 85%
- Small images left untouched
- Result: 409MB original reduced to 247MB (40% reduction)
Database URL Migration
Updated all Firebase Storage URLs to R2 URLs via SQL:
- 220 menu_items image_url rows updated
- 10 restaurants image_url rows updated
- 24 file_uploads download_url rows updated
Upload Endpoint
All file uploads go through the server-side endpoint at /api/uploads/route.ts.
Flow:
- Client sends file as FormData with optional
recordUpload=trueflag - Server authenticates request via Supabase JWT
- File uploaded to R2 via
uploadToR2(key, buffer, contentType) - If
recordUpload=true, server inserts a row intofile_uploadstable viasupabaseAdmin - Returns
{ url, key, fileUploadId }
Why server-side: Originally, fileUploadsRepo.ts inserted into file_uploads client-side using the browser Supabase client. This failed on Safari due to connection issues. Moved the insert to the server endpoint (March 2, 2026) which resolved the issue and centralizes all R2 operations.
Key fields stored in file_uploads:
name,size_bytes,content_typestorage_path(R2 key)download_url(public R2 URL)uploaded_by_uid,uploaded_by_emaildescription,uploaded_at
Note: The storage_type column was removed from the FileUpload model — it never existed in the database.
Access Configuration
- Public access enabled via R2.dev subdomain
- AWS CLI configured with dedicated profile for management
- CORS configured in Cloudflare R2 dashboard (Settings > CORS Policy) to allow cross-origin image loading:
- Allowed Origins:
https://ricoya.ipnoelp.com,https://ricoya.net,http://localhost:3000 - Allowed Methods: GET, HEAD
- Allowed Headers: Content-Type, Range
- Max Age: 86400s
- Allowed Origins:
Ownership
Account created under development credentials. Plan to transfer Cloudflare account ownership to client when ready for production handover.