How should I handle file uploads on serverless — presigned URLs, streaming, or multipart?

Handling file uploads on serverless platforms where request body size limits (Vercel 4.5MB, Lambda 6MB sync, Workers 100MB) force different architectural patterns depending on file size and platform

Presigned URLs for most cases. Streaming for large files when you need progress. Multipart only when the platform requires it.

Blockers

Who this is for

Candidates

Presigned URL Direct Upload (S3/R2/GCS)

Server generates a presigned upload URL; client uploads directly to object storage, bypassing the serverless function entirely. Works with any file size. S3 presigned URLs support up to 5GB single upload, 5TB multipart.

When to choose

When files exceed your platform's request body limit (Vercel 4.5MB, Lambda 6MB). Best for serverless + cost-sensitive constraints — the upload never touches your function, so no compute cost for transfer. The standard pattern for production file uploads on serverless.

Tradeoffs

No function compute cost for upload transfer. Works with any file size. Client uploads directly — reduces latency by eliminating the server relay. Requires CORS configuration on the storage bucket. Server-side validation must happen post-upload (via S3 event trigger or polling). Cannot validate file content before it's stored.

Cautions

Presigned URLs have expiration times (default 15 min, max 7 days for S3). Set Content-Type and Content-Length-Range conditions on the presigned URL to prevent abuse. Always validate uploaded files after storage — check file type, size, and scan for malware. Use S3 lifecycle rules to auto-delete uploads that fail validation.

Server-Side Relay (small files only)

Upload through the serverless function body. Limited by platform: Vercel 4.5MB (all plans), Lambda 6MB sync payload, Cloudflare Workers 100MB (Free/Pro), 200MB (Business), 500MB (Enterprise).

When to choose

When files are small enough to fit within platform limits and you need server-side validation before storage. Best for small-team + serverless where avatar uploads, CSV imports, or document uploads under 5MB need immediate validation (file type, content, virus scan) before acceptance.

Tradeoffs

Simplest implementation — standard multipart form handling. Server validates before storing. Function execution time and memory consumed during upload. Platform size limits are hard ceilings that cannot be increased. At Vercel's 4.5MB limit, even moderate image uploads may fail.

Cautions

Vercel's 4.5MB limit applies to the entire request body including headers and form fields — actual file capacity is slightly less. Lambda's 6MB limit is for synchronous invocation; async invocation payload is 1MB (increased from 256KB in October 2025). API Gateway has a separate 10MB request body limit. Lambda response streaming supports up to 200MB (increased from 20MB in July 2025). Always implement presigned URL fallback for files near the size limit.

Multipart Upload with Chunking (large files)

Split large files into chunks on the client, upload each chunk via presigned URLs, then complete the multipart upload. S3 supports 10,000 parts of 5MB-5GB each (up to 5TB total). R2 and GCS have similar multipart APIs.

When to choose

When uploading files larger than 100MB or when upload reliability matters (resume on failure). Best for cost-sensitive + serverless where video, backup, or large dataset uploads need resilient transfer without server-side infrastructure.

Tradeoffs

Resumable — failed chunks can be retried without restarting the entire upload. Parallel chunk upload for faster throughput. More complex client implementation (track upload parts, handle completion). Incomplete multipart uploads incur storage costs until aborted — configure lifecycle rules.

Cautions

S3 charges for incomplete multipart uploads until explicitly aborted. Set AbortIncompleteMultipartUpload lifecycle rules (e.g., 7 days). Libraries like @aws-sdk/lib-storage (Node.js), Uppy, or tus-js-client handle chunking client-side. For R2, the multipart API is S3-compatible but verify part size minimums (R2 requires 5MB minimum except last part).

Upload Service (UploadThing, Transloadit, Cloudinary)

Managed upload infrastructure handling storage, processing, and CDN delivery. UploadThing: free 2GB, $10/month for 100GB. Cloudinary: free 25 credits/month (credits are fungible across bandwidth, storage, and transformations). Transloadit: $69/month Startup plan (40GB included).

When to choose

When you need image/video processing (resize, transcode, optimize) in addition to upload, and building it yourself exceeds small-team capacity. Best for small-team + cost-sensitive where the total cost of presigned URLs + processing Lambda + CDN exceeds a managed service.

Tradeoffs

Handles upload, processing, and delivery — one vendor for the entire media pipeline. UploadThing integrates directly with Next.js. Cloudinary's free tier (25 credits) covers small apps but credits are shared across bandwidth, storage, and transformations — not dedicated bandwidth. Vendor lock-in for media URLs. Processing costs can spike with video transcoding. Migration requires re-pointing all stored media URLs.

Cautions

UploadThing is Next.js-focused — evaluate framework compatibility for other stacks. Cloudinary uses a credit-based pricing model where 1 credit = 1GB bandwidth OR 1GB storage OR 1,000 transformations — realistic usage splits reduce effective bandwidth well below the headline number. Transloadit charges per encoding minute ($69/month Startup includes 40GB, $1.80/GB additional). Always have an exit strategy — store original files in your own S3/R2 bucket even when using a processing service.

Facts updated: 2026-03-15
Published: 2026-03-29

Try with your AI agent

$ npm install -g pocketlantern
$ pocketlantern init
# Restart Claude Code, Cursor, or your MCP client, then ask:
# "How should I handle file uploads on serverless — presigned URLs, streaming, or multipart?"
Missing something? Request coverage