EAZIP BLOG
Client-Side vs. Server-Side ZIP: Where Should the Archive Get Built?
The download-all architecture decision in one page: where the bytes flow, what each side's size ceiling really is, who pays the bandwidth, and the cases where each approach — or a managed third option — clearly wins.
Every "download all as ZIP" feature answers one architectural question first: does the archive get built in the user's browser or on a server? Everything else — cost, size limits, reliability, how much code you own — follows from that choice. This page is the decision, made explicit.
The short answer
| Client-side (browser) | Server-side (yours) | |
|---|---|---|
| Bytes flow | Storage → browser | Storage → server → browser |
| Bandwidth cost | One storage egress per download | Storage egress plus your server's, per download |
| Size ceiling | Browser memory; tab must stay open | Server memory/timeouts; streaming raises but doesn't remove it |
| Resume / progress | In-page progress; archive lost on reload | No Content-Length → no progress bar, no resume |
| Storage config | Bucket CORS required | None |
| Server code | A URL-signing endpoint only | The whole fetch-and-zip pipeline |
If you remember one heuristic: client-side wins on infrastructure (there nearly isn't any), server-side wins on control — and both share a ceiling that a third option exists to remove.
Where the bytes flow — and who pays
Client-side, each file travels storage → browser: one pass of storage egress per download, nothing through your servers. Server-side adds your own machine to the path; on egress-charging storage (S3 at ~$0.09/GB) a 20 GB export downloaded three times is ~$5.40 before your server's own bandwidth. The exception is zero-egress storage like R2, where proxying is operationally costly but financially free — the egress-cost breakdown runs the numbers per provider.
The ceilings are different in kind
The browser's ceiling is memory and lifetime: libraries like JSZip hold the archive in memory, streaming ones stretch it, but a multi-gigabyte ZIP in a tab is gambling either way — and a reload kills the job at 95% with nothing to resume.
The server's ceiling is time and delivery: streaming keeps memory
flat, but the response has no Content-Length, so the user gets no
progress bar and no Range resume, gateways impose timeouts (API Gateway's
~30 s, Lambda's hard 15 min), and a dropped connection restarts from
zero. Neither side fails gracefully; they just fail differently.
When client-side clearly wins
- The files are already reachable from the browser — public URLs, or signed URLs your endpoint returns anyway.
- Exports are human-scale: tens to hundreds of files, well under browser-memory territory.
- You want zero new infrastructure: an MIT library like Eazip.js plus a signing endpoint is the entire build.
- The user is present — it's a button, and in-page progress is a feature.
When server-side clearly wins
- No user is present: scheduled bundles, email-me-my-data flows, webhook-driven exports.
- The archive needs server-side inputs — generated files, database dumps, cross-bucket assembly.
- CORS is off the table: a compliance bucket that will never allow browser origins.
- Auth is complex enough that you want exactly one place touching storage.
The third option both sides escalate to
The shared ceiling — big archives, reliable delivery — is what a managed
job removes: the server decides what goes in the archive (a URL list),
and a service does the fetching, zipping, and delivery as an expiring
link with real Range/resume. Client-side code escalates by changing one
option (strategy: 'cloud' in Eazip.js); server-side code escalates by
POSTing the list instead of streaming
the bytes. Either way you keep your architecture and drop its ceiling —
at the price of a vendor and metered volume beyond free tiers.
FAQ
Is client-side zipping safe for private files?
Yes, with the same discipline as any download: the browser only receives short-lived signed URLs minted by an endpoint that authorized the user. Credentials never reach the client.
Which is faster for the user?
For small exports, client-side — no server hop, and fetching starts immediately. For large exports, neither: both hit their ceilings, which is precisely when the managed path with a prepared, resumable download wins on experience.
Can I do both?
That's the common end state: browser-side for everyday exports, the same signed-URL list to a managed job past a size threshold. The integration decision guide maps the exact switch points.
Does server-side always mean writing ZIP code?
No — that's the distinction between server-side execution and
server-side ownership. Owning it means archiver, ZIP64, retries, and
delivery are your code; the managed path keeps execution server-side
while the ZIP machinery is someone else's problem.