Object Storage
SaaS-Box includes a built-in file storage system using Cloudflare R2 and the AWS S3 SDK . I designed this integration to bypass serverless platform limitations, allowing your users to securely upload avatars, documents, and assets directly to the cloud.
Architecture Philosophy
When dealing with file uploads in a Next.js serverless architecture (like Vercel), you cannot simply send massive files through your Server Actions or API Routes. Serverless functions have strict maximum execution times (often 10 to 15 seconds) and payload size limits (typically 4.5MB).
To solve this, I utilize Presigned URLs . Instead of sending the file to my server, the client asks the server for a secure, temporary "ticket" (the presigned URL). The client then uses this ticket to upload the file directly to Cloudflare R2, completely bypassing my Vercel server limits.
Why Cloudflare R2?
Traditional AWS S3 charges you for data transfer out (egress fees), which can become extremely expensive as your app scales. Cloudflare R2 offers an S3-compatible API with zero egress fees .
S3 Compatibility Layer
Because R2 implements the standard S3 API, I use the official@aws-sdk/client-s3 library in my boilerplate. This guarantees enterprise-grade security and stability for your uploads.
Cloudflare R2 Setup
Follow these steps to initialize your bucket and obtain the necessary API credentials.
Create the R2 Bucket
Log in to your Cloudflare dashboard and navigate to R2 Object Storage . Click "Create bucket". Give it a descriptive name (e.g., "saas-box-uploads") and select a location hint closest to your primary user base.
Enable Public Access (Dev URL)
By default, buckets are completely private. Since we are using this bucket to serve user avatars publicly, go to the bucket settings and enable the R2.dev subdomain . This will give you a public URL to serve your images (e.g., pub-1234.r2.dev).
Generate API Credentials
Go back to the main R2 Overview page and click Manage R2 API Tokens . Create a new token with Object Read & Write permissions. Copy the Access Key ID, Secret Access Key, and your Account ID.
CORS Configuration
Critical step: You must allow your frontend domain to upload directly to R2.
Because the browser makes a direct PUT request to Cloudflare from your React frontend, the browser will block the request unless Cloudflare explicitly allows it via CORS (Cross-Origin Resource Sharing).
In your Cloudflare dashboard, go to your Bucket > Settings > CORS Policy . Click "Edit CORS policy" and paste the exact JSON configuration below. This configuration allows both your local development environment and your production Vercel app to upload files.
Note: Make sure to update the AllowedOrigins array with your actual production domain once you go live!
Environment Config
I mapped out the necessary environment variables in your.env file. They are validated in src/env.ts.
The Presigned URLs API
How the server securely generates upload tickets using the AWS SDK.
1. The S3 Client Setup
In src/lib/s3.ts, I initialize the S3 client using Cloudflare's specific endpoint formatting. Note that the region is strictly set to "auto" for R2.
2. The Server Action
In src/app/actions/profile.ts, I expose a Server Action to the frontend. This action verifies the user is logged in, generates a completely unique file name (to prevent malicious overwriting of other users' avatars), and returns the signed URL.
Client-Side Uploads
Now that the frontend has the uploadUrl, uploading is as simple as making a native fetch request with the PUT method. No third-party client libraries required!
Serving Public Files
Notice that the server action returns a publicUrl alongside the upload ticket. Because I enabled the R2.dev domain in Cloudflare, files uploaded to this bucket become immediately accessible via that public endpoint.
I take this publicUrl and save it directly to the image column in the Drizzle Database. This avoids the need to dynamically generate signed download links every single time a user loads their avatar on the dashboard, making the application infinitely faster and cheaper to run.
Frequently Asked Questions
What is the maximum file upload size?
Because we use presigned URLs, the files bypass Vercel entirely and are sent directly to Cloudflare R2. The limit for a single standard PUT request is 5GB! However, I highly recommend adding client-side validation to restrict file sizes to something reasonable (e.g., 5MB for avatars) to prevent users from filling up your storage.
How do I make private files? (e.g. Invoices)
If you need to upload private documents, you should create a separate, private R2 bucket (do not enable the .r2.dev public domain). When a user needs to view the document, you use the AWS SDK to generate a GetObjectCommand presigned URL that expires after a few minutes, ensuring strict access control.
Official Documentation
Dive deeper into Cloudflare R2 infrastructure and the AWS S3 Javascript SDK.