Large file uploads without taking the server down
Why a 200 MB file should never pass through your server at all, and what to do instead.
Published 2026-09-17
The obvious way to build a file upload is also the first one to break: the browser sends the file to your server, the server holds it, and then hands it on to storage. That works beautifully until somebody uploads a real file.
Bandwidth is not the problem. The problem is that platforms cap the size of a request, and the cap is far lower than most people expect.
On this site, an image uploaded to the content editor goes through a Route Handler rather than a Server Action. The reason is dull but it decides the design: Server Actions in Next cap the request body at one megabyte and throw a 413 above it. Raise that, and Vercel still has its own ceiling of roughly 4.5 MB. Two limits stacked on each other, and only one of them is yours to change.
Who should hold the file
On Fingerprint, users upload bundles of 3D design files, and those are not four megabytes. The architecture there takes the server out of the file's path entirely.
The server mints a short-lived signed URL against storage (Cloudflare R2, via @aws-sdk/client-s3 and s3-request-presigner), hands it back to the browser, and the browser uploads straight to storage. The server never sees a byte. What it did do was decide who is allowed to upload, to which path, and for how long that URL stays valid.
That distinction deserves its own sentence. The server's job is to grant permission, not to carry bytes.
Shrink it before it leaves
On Fingerprint the files are bundled into an archive in the browser, with JSZip, before anything is sent. That sounds like a detail and it buys two things: fewer bytes on the wire, and one upload instead of twenty separate requests each of which can fail on its own.
There is a smaller version of the same idea on this site. An image uploaded to the content editor is re-encoded to WebP at quality 78 before it is stored, so a photo off a phone lands at the weight of a purpose-made illustration rather than its original size.
What the server still owns
Once the file stops passing through you, three decisions remain that cannot move to the browser, because the browser is not a source of truth:
- Who may upload, and to which path. The signed URL is minted server-side after the check passes, not before it.
- What may be uploaded. File type and size are checked server-side even if the browser checked already, because a check in the browser is a convenience for the user rather than enforcement.
- What happens next. A file being in storage does not mean the system knows about it. A row has to be written server-side, or you end up with files nobody can find.
The third is the one that gets forgotten. It is easy to confirm an upload worked and hard to discover a month later that half of what is in storage is linked to nothing.
Check this early, not at launch
Before building: what is the request ceiling on the platform you are on, and what is the largest file a user will genuinely try to send. If the second number is bigger than the first, the architectural decision has already been made for you, and it is much cheaper to know that on day one.
The same idea, that the browser is a display and not a source of truth, runs through payments as well. We wrote about it in connecting site payments to Green Invoice.
Related services
Have a question about your business?
Tell us what is not working today and we will tell you what is worth building.
Fale conosco