ZIP compression in the browser used to require a server round-trip. JSZip changed that. Here's a practical guide to creating and extracting ZIPs client-side.
Creating a ZIP
import JSZip from 'jszip';
async function createZip(files) {
const zip = new JSZip();
for (const file of files) {
const buffer = await file.arrayBuffer();
zip.file(file.name, buffer, { compression: 'DEFLATE', compressionOptions: { level: 6 } });
}
const blob = await zip.generateAsync({
type: 'blob',
compression: 'DEFLATE',
compressionOptions: { level: 6 }
});
return blob;
}
Compression level 6 is the DEFLATE default — a good balance between size and speed. Level 9 gives ~5% better compression but takes noticeably longer in the browser.
Extracting a ZIP
async function extractZip(file) {
const zip = await JSZip.loadAsync(file);
const entries = [];
zip.forEach((relativePath, zipEntry) => {
if (!zipEntry.dir) {
entries.push({ path: relativePath, entry: zipEntry });
}
});
return entries;
}
async function downloadEntry(zipEntry, filename) {
const blob = await zipEntry.async('blob');
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
File Size Limits
Browser memory is the real constraint. A 500MB ZIP will likely crash the tab. For large files, you need a streaming approach — JSZip supports generateInternalStream() for this, but it's more complex.
For typical use (documents, images, small assets), staying under 100MB works fine.
What Doesn't Compress
Already-compressed files (JPG, PNG, MP4, MP3) gain almost nothing from DEFLATE. Sometimes they get slightly larger. A useful optimization: detect these types and skip compression:
const SKIP_COMPRESSION = ['jpg', 'jpeg', 'png', 'mp4', 'mp3', 'webp', 'gif', 'zip', 'gz'];
function shouldCompress(filename) {
const ext = filename.split('.').pop().toLowerCase();
return !SKIP_COMPRESSION.includes(ext);
}
Live at toolzip.app/tools/zip-tool — no upload, no server.