EnorexaEnorexa
Back to blog

Engineering

Why 5MB File Uploads Crash Next.js API Routes (And How S3 Multipart Fixes It)

November 4, 20264 min readBy Enorexa Team

Drop a 6MB enterprise contract PDF into a standard Next.js API route on Vercel or AWS Lambda, and your user immediately hits a hard 413 FUNCTION_PAYLOAD_TOO_LARGE error. Implementing a direct nextjs s3 multipart upload pipeline is the only reliable way to move large files from the browser into object storage without blowing past serverless request limits or exhausting Node.js heap memory. Most engineering teams try to work around the 4.5MB serverless body limit by spinning up a dedicated Express container just to proxy multipart/form-data streams. That fixes the 413 error on day one. By month three, three concurrent 200MB video uploads saturate your container's network interface, spike event-loop lag above 800ms, and double your cloud egress bill because every byte travels through your compute layer twice.

Why Single S3 Presigned URLs Fail on Unstable Connections

When teams finally abandon Node.js file proxying, they usually reach for a single PutObjectCommand presigned URL. It works fine for a 2MB avatar. It falls apart completely when an operations manager on patchy hotel Wi-Fi uploads a 150MB zip archive or a 4K inspection video. Single-PUT uploads are all-or-nothing over a single HTTP connection; if a TCP socket drops at 94%, the browser throws net::ERR_NETWORK_CHANGED and the user has to restart the entire transfer from byte zero. Worse, modern AWS SDK v3 clients automatically append x-amz-checksum-crc32 query parameters to presigned URLs unless explicitly configured otherwise, triggering cryptic 403 SignatureDoesNotMatch rejections when the browser's fetch call doesn't compute and attach the exact matching CRC32 header.

Architecting S3 Presigned Multipart Uploads in Next.js

The production fix is splitting files in the browser using Blob.prototype.slice() and orchestrating a three-step S3 multipart handshake through lightweight Next.js Server Actions or Route Handlers. First, your backend calls CreateMultipartUploadCommand and returns the UploadId along with an array of presigned UploadPartCommand URLs—one for each 10MB chunk. Note AWS's strict minimum: every part except the final trailing chunk must be at least 5,242,880 bytes (5MB), or S3 rejects the commit with an EntityTooSmall XML error. The browser uploads 3 to 4 slices concurrently using XMLHttpRequest (or fetch with a readable stream tracker for progress bars) and reads the ETag response header from each completed part.

Here is the trap that breaks 90% of first-time implementations: S3's default CORS policy hides the ETag header from browser JavaScript. Even though the PUT request returns 200 OK, response.headers.get('ETag') evaluates to null in the client, causing your final CompleteMultipartUploadCommand call to fail with InvalidPart. Your S3 bucket CORS configuration must explicitly declare "ExposeHeaders": ["ETag"] alongside allowed PUT and POST methods. Once all chunks finish, the client sends the ordered [{ PartNumber, ETag }] manifest back to your Next.js API route, which verifies tenant ownership in PostgreSQL, commits the assembly on S3 in under 50 milliseconds, and triggers downstream processing.

Preventing Orphaned Multipart Chunks and Hidden Storage Bills

Aborted browser tabs create a silent financial leak in S3. When a user closes their laptop mid-upload after pushing 80MB of a 200MB file, those uploaded parts sit in an incomplete multipart state indefinitely. They do not appear in standard ListObjectsV2 bucket views in the AWS Console, yet AWS bills you standard $0.023/GB storage rates for every unmerged chunk. Never rely solely on client-side window.onbeforeunload hooks to fire AbortMultipartUploadCommand—browsers routinely kill background requests when a tab closes.

Instead, enforce cleanup at the infrastructure layer. Every production S3 bucket handling multipart uploads needs an automated Lifecycle Rule with AbortIncompleteMultipartUpload set to DaysAfterInitiation: 1 (or 7 days if you build resumable multi-day upload sessions saved in IndexedDB). Pair that with a database state table tracking PENDING, COMPLETED, and FAILED uploads so your workers only trigger OCR, video transcoding, or Enorexa Tools document pipelines after S3 emits an s3:ObjectCreated:CompleteMultipartUpload event. If your team is fighting serverless payload ceilings, sluggish file ingestion, or brittle document workflows in Next.js, explore our architecture work at Enorexa or talk to us to audit your upload pipeline.