DEV Community

137Foundry
137Foundry

Posted on

Why Chunked Uploads Beat a Single PUT Request for Large Files on Mobile Networks

Every upload form starts the same way: a single fetch or XMLHttpRequest call with the file attached as the body. It works in local testing. It works on office Wi-Fi. Then it ships, someone tries to upload a 300MB export from a phone on spotty LTE, the connection drops at 80 percent, and the whole request fails with nothing to show for it.

That's not a rare edge case. It's the default outcome for any single-request upload once the file gets big enough and the network gets unreliable enough, and both of those things are just normal conditions for a meaningful slice of real users.

What Actually Breaks

A single PUT or POST carrying an entire file has exactly one point of failure: the whole request. There's no partial credit. If 290MB of a 300MB file made it to the server before the connection dropped, none of that matters, because the server never got a complete request body to act on. The client has no way to say "I already sent most of this, just send the rest."

Reverse proxies add a second failure mode. Nginx and most load balancers have request timeout settings, and a single request carrying a multi-hundred-megabyte body can hit that ceiling even on a perfectly healthy connection if the file is large enough and the timeout is tuned for typical API traffic rather than file transfer.

There's a memory cost too. A server that buffers the entire incoming request body before writing anything to storage is holding the whole file in memory at once. Do that for a handful of concurrent large uploads and memory pressure becomes a real operational problem, not a theoretical one.

Splitting the File Changes the Failure Math

Chunking doesn't eliminate network failures. It changes what a failure costs. Instead of one request carrying 300MB, the client sends the file as, say, thirty requests of 10MB each. If the connection drops on request eighteen, the other seventeen already succeeded and stay succeeded. The client only has to retry the one chunk that failed, not the whole file.

That single change turns an unreliable network from "makes large uploads basically impossible" into "makes large uploads slightly slower on bad days." A dropped chunk costs ten seconds of retry, not the ten minutes it took to get to 80 percent the first time.

It also opens the door to genuinely resuming a session that gets abandoned entirely, closing the tab, killing the app, losing signal for twenty minutes, as long as the server keeps a record of which chunks it already has. Without chunking, there's nothing to resume; the request itself is the only unit of work, and it's already gone.

The Cost You're Actually Trading Against

Chunking isn't free on the client either, it's more request overhead, more state to manage in the browser (which chunks have been sent, which are pending, which failed), and more code paths to test. It's worth naming that cost honestly rather than treating chunking as a strictly better default for every upload. For a small file on a reliable connection, a single request is simpler to implement, simpler to debug when something does go wrong, and has fewer moving parts that could themselves introduce a bug.

The trade only pays off once file size and network unreliability cross a threshold where the failure cost of a single request start outweighing the added complexity of chunking. That threshold is specific to your product's actual users, not a fixed number you can look up, which is why it's worth measuring your own current upload failure rate before deciding this is worth building rather than assuming it based on general best practice.

The Server Has to Cooperate

Chunking on the client only pays off if the server-side handling matches. That means an endpoint that accepts one chunk at a time, a lightweight record (even just a database row) tracking which chunk indexes have landed for a given upload session, and a way for the server to verify each chunk's integrity before acknowledging it, typically a checksum sent alongside the chunk.

The IETF's HTTP Range Requests specification is worth reading even if your implementation doesn't use literal Range headers, because the pattern it describes, ask what's missing, send only that, is the same one a good chunked upload protocol follows regardless of the exact header names involved.

If you'd rather not hand-roll this, tus is an open resumable upload protocol with client and server libraries across most major stacks, and it solves the chunk-tracking problem without you having to design the wire format yourself. For teams already on AWS, S3's multipart upload support does something very similar natively for object storage.

The Browser Side Is Simpler Than It Sounds

Slicing a File object into chunks doesn't require reading the whole file into memory first. The File and Blob APIs support lazy slicing, so a client can walk through a large file one chunk at a time, only holding the current chunk in memory, which matters a lot on lower-end mobile devices where memory is scarce and background tabs get killed aggressively.

How Chunk Size Actually Changes the Math

Picking a chunk size isn't arbitrary, and it's worth thinking through rather than copying a default. Smaller chunks (1 to 2MB) mean a dropped connection costs almost nothing to retry, but you pay more in per-request overhead: HTTP headers, TLS handshake reuse aside, and server-side bookkeeping for every single chunk. Larger chunks (20MB and up) cut that overhead but mean a mid-chunk failure throws away more work before the client even notices.

For most web uploads, 5 to 10MB lands in a reasonable middle ground: small enough that a retry is cheap and fast, large enough that request count stays sane even for a multi-gigabit file. If you know your users are specifically on very poor connections, cellular in low-signal areas, satellite links, shrinking the chunk size further is a reasonable trade even though it means more requests overall. The right number depends more on your actual users' networks than on a rule of thumb, so it's worth testing a couple of sizes against real throttled connections before locking one in.

A Concrete Example: Video Upload From a Field App

Picture a field inspection app where technicians upload a two-minute video, roughly 150MB, from a job site with patchy LTE. With a single-request upload, here's what typically happens: the request gets to 60 to 90 percent over two or three minutes, the connection blips for a couple of seconds as the technician walks between buildings, and the whole thing fails. They retry. Same thing happens again. After two or three attempts, most people give up and either try again later from a different location or don't upload the video at all.

With chunking at 10MB per chunk, that same video is fifteen chunks. A blip during chunk eleven costs a retry of one 10MB piece, a few seconds, not a restart of the full 150MB, several minutes. If they lose signal entirely and only get back online later, the app can resume from chunk eleven onward once the server confirms the first ten already landed. The technician doesn't experience this as "the upload got smarter." They experience it as "uploads just work now," which is the actual bar to hit.

Testing This Before It Ships to Real Users

The failure modes chunking is meant to fix don't show up in normal local development, because your dev machine's network doesn't behave like a job site's does. Before calling a chunked upload implementation done, it's worth deliberately simulating the conditions it's supposed to handle: throttle bandwidth down to something closer to real mobile speeds, kill the connection mid-transfer (not just close the tab, actually drop the network), and confirm the client both detects the failure cleanly and can resume without duplicating or corrupting data.

It's also worth testing the case where a chunk's acknowledgment gets lost even though the chunk itself was written successfully. Send the same chunk index twice on purpose and check that the server treats the second one as a safe no-op instead of an error or a duplicate write. This is the scenario that separates a real resumable implementation from one that only looks resumable in the demo.

When It's Not Worth It

None of this is free complexity to add everywhere. A 2MB avatar upload doesn't need chunking; the failure cost of a single dropped request is trivial and retrying the whole thing is cheap. The threshold where chunking starts paying off is usually somewhere in the tens of megabytes, and it depends more on your users' actual network conditions than on a fixed file size. If your product's uploads are mostly small and your users are mostly on reliable connections, this is a solved problem you don't need to solve again.

But if your app has users uploading video, large datasets, or anything in the hundreds-of-megabytes range from mobile networks, a single-request upload isn't a simplification, it's a reliability gap waiting to show up in a support ticket. 137Foundry wrote up a fuller breakdown of the chunk protocol design, server-side state tracking, and resume flow in a longer guide on building resumable uploads if you're planning to build this out properly.

Top comments (0)