A “Download all as ZIP” button in React starts simple. A production version also needs progress, cancellation, retry, useful errors, and a plan for archives that are too large for browser memory.
In this tutorial, we’ll use Eazip, an open-source ZIP toolkit for JavaScript and React. Its React package gives you a hook for starting ZIP jobs and a ready-made tray for showing their status.
Everyday files can be zipped entirely in the browser. When the same feature needs to handle multi-GB archives or thousands of remote URLs, it can move the job to Eazip Cloud without adding any backend code.
Install the React package
npm install @eazip/react
@eazip/react requires React 18 or later. It includes the core ZIP engine, so you do not need to install another Eazip package.
Build a working ZIP download component
This component lets a user select several files and download them as one ZIP:
import { useState } from 'react';
import { EazipTray, useEazip } from '@eazip/react';
export function FileZipDownload() {
const [files, setFiles] = useState<File[]>([]);
const zip = useEazip();
return (
<section>
<label>
Files to download
<input
type="file"
multiple
onChange={(event) =>
setFiles(Array.from(event.currentTarget.files ?? []))
}
/>
</label>
<button
type="button"
disabled={files.length === 0 || zip.isBusy}
onClick={() =>
zip.download({
files,
zipName: 'selected-files.zip',
})
}
>
Download {files.length || ''} files as ZIP
</button>
<EazipTray />
</section>
);
}
There are three Eazip pieces in this example:
-
useEazip()gives the component its download commands and current task. -
zip.download()starts the ZIP job and returns immediately. -
<EazipTray />shows progress, cancel, retry, partial results, errors, and the completed download.
No provider or CSS import is required.
What happens to the selected files?
Without a strategy option, Eazip uses its Local strategy. The selected File objects stay on the user’s device and the ZIP is created in the browser.
That means there is:
- no upload before the download can begin;
- no server-side ZIP code to write;
- no temporary archive to store and clean up.
The same files option also accepts FileList, Blob, URL strings, and source objects when you need to rename entries or create folders inside the ZIP:
zip.download({
files: [
{ file: reportFile, filename: 'reports/annual.pdf' },
{ file: chartBlob, filename: 'reports/chart.png' },
],
zipName: 'reports.zip',
});
Remote URLs used in the browser must allow your application’s origin through CORS.
Handle multi-GB archives
Browser-side zipping is a good default, but browser memory and tab lifetime become limiting factors for multi-GB archives or thousands of files.
For a large archive built from remote URLs, keep the same hook and tray and change the execution strategy:
zip.download({
strategy: 'cloud',
publicKey: 'pk_ez_...',
files: urls,
zipName: 'media-export.zip',
});
The ZIP job now runs on Eazip Cloud while the tray continues to show its progress and result. Cloud jobs can handle multi-GB archives, thousands of URLs, and jobs that need to survive a page reload.
The pk_ez_... value is a publishable browser key. Create one in Eazip and restrict it to your application’s allowed origins. No secret key or backend endpoint is needed for this frontend flow.
Cloud jobs use remote URL sources. Files that exist only as browser File or Blob objects should stay with the Local strategy because Eazip Cloud cannot access data that only exists on the user’s device.
Local or Cloud?
| Use Local | Use Cloud |
|---|---|
| The files are already in the browser | The sources are remote URLs |
| The archive fits comfortably in browser memory | The archive is multi-GB or contains thousands of URLs |
| Files should never leave the device | The job must continue outside the browser tab |
| You want zero setup | You can add a publishable key and allowed origin |
Both strategies use the same useEazip() hook and <EazipTray />, so the visible download flow does not need to be rebuilt when the workload grows.
Where should the tray live?
If several components can start ZIP downloads, render one <EazipTray /> near the application root. Individual buttons can keep calling zip.download(), while the tray owns the visible task state.
If the status must fit an existing panel or toolbar, use the hook state directly and build a custom interface instead.
The complete examples and API details are available in the Eazip React ZIP download guide.
How does your React application handle “Download all” today—and what happens when the archive grows beyond browser memory?
Top comments (0)