← DeployCloud

File Uploads & Storage Guide

Routing uploads through your API server doubles bandwidth, ties up compute on slow clients, and slams into platform body limits — Vercel functions cap request bodies at 4.5 MB. The standard pattern is direct-to-bucket upload with a presigned URL.

Every major cloud offers S3-style object storage: Amazon S3 itself, Cloudflare R2 with its S3-compatible API and zero egress fees, Google Cloud Storage, Azure Blob Storage and DigitalOcean Spaces. The flow below works on all of them.

The presigned upload flow

The client asks your backend for permission; the backend authenticates the user, validates declared type and size, and returns a short-lived presigned URL; the client PUTs the file straight to the bucket; a follow-up call or bucket event notification records the object key in your database. On S3, createPresignedPost policies can enforce a content-length-range so the bucket itself rejects oversized files; elsewhere, verify size when you record the object.

💬 Chat with our AI →

Validate like you mean it

Never trust the client's Content-Type — sniff magic bytes server-side before treating anything as an image or PDF. Generate random object keys (UUIDs) instead of user filenames, keep metadata in your database, and strip EXIF location data from photos. For user-shared documents, scan with ClamAV in a background job before making files visible to anyone else.

💬 Chat with our AI →

Serving and transforming

Keep buckets private. Serve public assets through a CDN — R2 pairs with a custom domain behind Cloudflare's cache — and gate private files with short-lived signed GET URLs. For images, resize on demand with Cloudflare Images, Cloudinary or imgix, or pre-generate sizes with sharp in a queue worker; never resize synchronously inside the upload request.

💬 Chat with our AI →

Large files and cleanup

Use multipart uploads for anything past roughly 100 MB so parts retry independently, and reach for the tus protocol via Uppy when uploads must survive network drops. Set bucket CORS to your exact origins, and add lifecycle rules that abort incomplete multipart uploads and expire temporary prefixes so half-finished junk stops accruing storage.

💬 Chat with our AI →

Frequently asked questions

Why not just upload through my API server?

You pay bandwidth twice, block workers on slow clients, and hit body-size ceilings. Presigned URLs hand the heavy lifting to object storage.

Should my bucket be public?

No. Keep it private, publish public assets through a CDN, and issue short-lived signed URLs for private downloads.

How is Cloudflare R2 different from S3?

R2 speaks the S3 API, charges nothing for egress bandwidth, and binds natively to Workers. S3 offers the deeper ecosystem — storage classes, replication, analytics.

How do I cap upload size?

Enforce it twice: a content-length-range condition in the presigned POST policy on S3, plus a server-side size check before the file is used.

💬 Chat with our AI →

💬 Chat with our AI →

Generate a blueprint free →

Explore the full desk on the home page →