Eazip
Guides

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 with cursor before 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 sets content-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.