If you've ever had to set up a backend API just to zip and download files (Google Drive, OneDrive etc.), you know it can be a pain in the nacelles—and it costs you additional infrastructure. This article shows you a simpler way - how to let your users zip files right in their browser. No backend required. 🌕🏎️ "moon lambo" your infrastructure cost saving.
Why did I write this? , which only works in modern browsers.
No Compression – Files download in their original size unless you've already got them compressed on the server.
File Availability – You need a way to serve the files securely to the browser, like with presigned URLs or similar.
How It Works
In a compatible browser, we can progressively enhance our app to zip and download files directly on the client. Here’s the idea:
Request Files Individually – The client requests each file directly.
Stream Files to Zip Archive – Using the File System Access API, the browser writes the files into a zip archive on the user's device.
No Storage/Infra Cost – Since the zipping happens locally, you're not paying to store the zip files or the compute required to support this.
For cases where data transfer is free (there are providers other than AWS out there, shocking I know), this method is a big win. No backend processing, no zip storage, no hassle. It’s fast, it’s efficient, and your users get their files in one clean download.
So let's dive in to what this looks like.
Tooling Up: Zip.js
To make this all work smoothly, we’re going to use one package: makes this process efficient by handling each file as a stream and sending requests with Range headers. This avoids loading the entire file into memory.
Here’s how the code looks:
CODEfor (const file of files) {
// Add each file to the zip archive
await zipWriter.add(file.name, new HttpRangeReader(file.url), {
uncompressedSize: file.size,
async onprogress(progress, total) {
// Update the progress for the current file
// (could be displayed in a progress bar)
console.log('entry progress:', file.name, progress, total);
},
});
}
In this example, each
addcall waits until the previous file completes streaming otherwise we risk saturating the network (you could have a worker pool here to process multiple but that's beyond this article). We’re also updating progress in real-time—great for giving users feedback if you want to display progress bars or status indicators.
With this, your users can download a custom zip of files without touching a backend. Just efficient, on-the-fly zipping directly in the browser.
Step 4: Cleaning It All Up
To wrap things up, make sure you properly close the zip archive. This final step ensures all data is written correctly, producing a valid zip file for download.
Simply call:
CODEawait zipWriter.close();
This command flushes the zip data to the file, ensuring there are no loose ends. Without it, the zip file might end up incomplete or corrupted, which isn’t ideal for your users!
The Full Code
If you're ready to see it all together, here’s the complete code sample for client-side zipping:
CODEimport {
HttpRangeReader,
TextReader,
ZipWriter,
configure,
} from '@zip.js/zip.js';
// this is the file we want to download, it should be returned from your
// api/database or whatever you are using to store references to files
type DownloadFile = {
url: string;
name: string;
size: number;
};
// configure the zip js library to use a chunk size of 5MB
// you can tweak this if you want, a larger size means more
// risk of network failure and having to retry the chunk,
// a smaller size means more chunks and longer time
configure({ chunkSize: 5000 * 1024 });
export async function downloadAsZip(files: DownloadFile[]) {
// check if the browser supports `showSaveFilePicker`
const compatible = typeof window['showSaveFilePicker'] === 'function';
if (!compatible) {
// handle your fallback here, e.g. window.open each file
return;
}
// showSaveFilePicker is not part of the standard types yet
// (hence the comment of using a modern browser)
const showSaveFilePicker = (window as any).showSaveFilePicker;
// get the file handle for the zip file
const handle: FileSystemFileHandle = await showSaveFilePicker({
suggestedName: 'download.zip',
startIn: 'downloads',
types: [{
description: 'ZIP Files',
accept: { 'application/zip': ['.zip'] },
}],
});
// create a writable stream to the file handle
const writable: FileSystemWritableFileStream = await handle.createWritable({
keepExistingData: false,
});
// create a new zip writer
const zipWriter = new ZipWriter(writable, {
compressionMethod: 0,
passThrough: true,
bufferedWrite: true,
});
for (const file of files) {
// add each file to the zip
await zipWriter.add(file.name, new HttpRangeReader(file.url), {
uncompressedSize: file.size,
async onprogress(progress, total) {
// log the progress of the file download to the user
// e.g. in a progress bar
console.log('entry progress:', file.name, progress, total);
},
});
}
// cleanup the zip
await zipWriter.close();
}
↗ Original-Artikel auf dev.to lesenVollständiger Original-BerichtAusführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
SOCIAL SHARE CARD GENERATOR