Download Vercel Blob Files as a ZIP
List a user's Vercel Blob files with list(), then zip and download them in the browser with Eazip — no server egress or storage credentials client-side.
To let a user download several Vercel Blob files as one ZIP, list the blobs they're allowed to see in a route handler, then hand the resulting URLs to Eazip in the browser. Eazip fetches each blob and builds the archive on the visitor's device, so your server only lists files — it never streams the bytes into a ZIP itself.
Loading live demo…
List blobs in a route handler
Your route handler should authenticate the request, scope list() to that
user's own prefix, and return only the fields Eazip needs:
import { NextResponse } from 'next/server';
import { list } from '@vercel/blob';
export async function GET(request: Request) {
const userId = await authenticate(request); // your auth check
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const prefix = `exports/${userId}/`;
const { blobs } = await list({ prefix });
const files = blobs.map((blob) => ({
url: blob.url,
filename: blob.pathname.slice(prefix.length),
}));
return NextResponse.json(files);
}list() accepts prefix so you never return another user's files by
mistake, and it's the only place that decides which blobs the caller may
export. Slicing the prefix off pathname keeps the folder structure inside
the ZIP relative to the export instead of exposing your full storage layout.
Private stores need a signed URL, not the raw one
blob.url is directly fetchable only for a public Blob store. For a
private store, every read requires authentication, so hand Eazip a
presigned URL instead: call issueSignedToken() and presignUrl() with
operation: 'get' for each blob, and set validUntil long enough to cover
the whole ZIP build. See Vercel Signed
URLs for the exact
calls.
Create the ZIP in the browser
Fetch the file list from your endpoint and pass it straight to createZip:
import { createZip } from '@eazip/core';
const response = await fetch('/api/exports/files', {
credentials: 'include',
});
if (!response.ok) {
throw new Error('Could not prepare export files');
}
const files = await response.json(); // { url, filename }[]
const result = await createZip({
files,
zipName: 'my-files.zip',
});
result.download();Public Vercel Blob URLs are served with permissive CORS, so the browser can
fetch them directly — no proxy route or storage credentials needed on the
client. If you're using presigned URLs for a private store, they work the
same way: the signature travels in the query string, not a header, so a
plain fetch() is enough.
When to use Eazip Cloud
Local execution above covers most exports. Move the same job to Eazip Cloud when:
- the export is multi-gigabyte or includes thousands of blobs;
- the job needs to survive a page reload or a closed tab;
- you don't want archive bytes occupying the visitor's tab memory; or
- a trusted backend, not the browser, should decide the file list.
const result = await createZip({
strategy: 'cloud',
publicKey: 'pk_ez_...',
files,
zipName: 'my-files.zip',
});
result.downloadAll();Cloud fetches the same URLs from Eazip's servers instead of the browser, so
it needs the Vercel Blob hostname allowed as a source host rather than
browser CORS. If you're signing URLs for a private store, make sure
validUntil covers the time Cloud needs to fetch every file, not just the
first one. See When to use Eazip Cloud for the full
comparison with Local.
Limits and alternatives
list()returns at most 1,000 blobs per call and is billed as an Advanced Operation; page through very large exports withcursorbefore you build the file list.- Downloading the blobs — whether the browser fetches them for a Local job or Eazip Cloud fetches them on your behalf — counts as normal Blob Data Transfer on your Vercel account. Eazip doesn't remove that cost; "zero-egress" on the Cloud side means Eazip itself doesn't add a separate bandwidth fee on top.
- If a user only needs one file, skip Eazip and link directly to the blob's
downloadUrl, which already setscontent-disposition: attachment— a ZIP only pays for itself once there's more than one file.
FAQ
How do I download all of a user's Vercel Blob files as one ZIP?
List the blobs under that user's prefix on the server with list({ prefix }), map the result to { url, filename }, and pass that list to
createZip in the browser. See List blobs in a route
handler above.
Can I zip Vercel Blob files without routing the bytes through my server?
Yes, for a public store. Eazip fetches each blob's URL directly from the
browser and builds the ZIP client-side; your route handler only calls
list(), it never streams file content.
How do I zip files from a private Vercel Blob store?
Sign each blob's URL with issueSignedToken() and presignUrl() before
returning it — a raw blob.url from a private store requires an
Authorization header that Eazip's browser fetch doesn't send. A presigned
URL carries its own signature in the query string, so it works the same way
a public URL does. See the callout in List blobs in a route
handler.
Does listing and zipping Vercel Blob files cost extra?
list() is billed as an Advanced Operation, and each file download counts
as Blob Data Transfer, the same as any other access to that blob — zipping
doesn't add a separate fee beyond normal Vercel Blob usage. See Vercel Blob
pricing for current
rates.
How many Vercel Blob files can I zip in the browser?
There's no hard limit from Eazip, but a Local job still runs in one browser
tab, so very large or numerous files use tab memory and time. For
multi-gigabyte exports or thousands of blobs, use Eazip
Cloud instead. See Input types for
every shape createZip accepts, and Create a ZIP from remote
URLs for the general URL-list
workflow.
Download MinIO Objects as a ZIP
List objects in a self-hosted MinIO bucket, presign each one, and package the results into one ZIP with @eazip/core.
Download UploadThing Files as a ZIP
Resolve a user's UploadThing uploads into signed URLs on the server, then fetch and zip them in the browser with Eazip.