JavaScript Compression Streams Table

PieceWhat it doesField note
new CompressionStream('gzip')A compressing pipegzip / deflate / deflate-raw - the three formats
blob.stream().pipeThrough(cs)Compose the flowBlob in, compressed stream out - no manual chunk loops
new Response(cs.readable).blob()Collect the resultThe read side wraps into a Blob for saving/sending
DecompressionStreamThe mirrorSame API, reversed - roundtrip in 4 lines each way
Streams compositionBackpressure includedWorks with fetch/file streams - pipeTo uploads compressed
Server must knowThe pairing contractCompressing what the server already gzips DOUBLE-compresses
Client-side winBefore uploadBig JSON payloads shrink 5-10x pre-send on slow uplinks
NOT a zip archiveSingle stream onlyNo multi-file containers - that is still a library
Reference: the MDN Compression Streams reference. CompressionStream brings gzip to the client as a PIPE: blob.stream().pipeThrough(new CompressionStream('gzip')) and the bytes flow through compressed - composed with fetch and file streams, backpressure handled by the stream machinery. Bottom line: the win is client-side pre-send compression (big JSON shrinks 5-10x on slow uplinks) and local archive-like storage; the two contracts to respect: the server must know not to double-compress, and this is a single stream - not a multi-file zip archive. Related tools: streams table (the pipe machinery), typed arrays table (the byte chunks), and file API table (the blobs flowing through).

CompressionStream brings gzip to the client as a PIPE: blob.stream().pipeThrough(new CompressionStream('gzip')) and the bytes flow through compressed - composed with fetch and file streams, with backpressure handled by the stream machinery.

Bottom line: the win is client-side pre-send compression - a big JSON payload shrinks 5-10x before a slow uplink carries it - and local compression for storage. The two contracts to respect: the server must know not to double-compress what arrives already gzipped, and this is a single stream, not a multi-file zip archive.

The honest part: the roundtrip is four lines each way because the API has no options - no compression levels, no archive structure, no per-file entries. It is a codec pipe; zip libraries still own containers, and HTTP transfer-encoding still owns transport compression.

How to use

  1. Compress before upload: const gz = await new Response(blob.stream().pipeThrough(new CompressionStream('gzip'))).blob(); - send gz with a Content-Encoding hint header.
  2. Roundtrip in four lines: compress through CompressionStream, collect via Response blob; reverse through DecompressionStream - symmetric, no options to remember.
  3. Compose with other stages: stream.pipeThrough(compressor).pipeThrough(encryptor).pipeTo(upload) - ordering is the design (compress then encrypt).

Frequently asked questions

Why compress on the client when servers gzip responses?

Different direction, different owner. Server gzip compresses RESPONSES going to the client - universal and free. Client-side compression compresses what the CLIENT SENDS: uploads on residential and mobile uplinks are far slower than downlinks, and a 10MB JSON payload shrinking to 1MB before the send is often the difference between a snappy save and a spinner. It also reduces storage costs for client-archived data and enables compressed data exchange between clients (through a server that just relays). The contract that comes with it: the receiving server must KNOW the payload is gzipped - decompress on receipt instead of applying its own response gzip to already-compressed bytes, which wastes CPU and can even grow data.

How does the pipe composition actually work end to end?

Every piece is a stream. The source (a Blob's .stream(), a fetch response body, a file handle stream) pipes THROUGH CompressionStream - a TransformStream that gzips chunks as they pass - and into a sink: another pipe, a Response for collection, or pipeTo a writable. Backpressure is native: a slow sink slows the compressor slows the source, so memory stays bounded on multi-gigabyte inputs. The collect idiom - new Response(compressedStream).blob() - wraps the readable in Response purely to reuse its blob() convenience. Ordering matters when composing: compress then encrypt (encryption's output is incompressible noise, so the reverse order wastes the compression).

What are the real limits - why is this not a zip library?

The API is a single-stream codec: one format (gzip, deflate, deflate-raw), no options, no container. There is no way to add multiple FILES with names and directories (zip's container format), no compression-level tuning (the implementation picks), no header/comment metadata, and no streaming random access into an archive. Those needs - bundling, partial extraction, encryption-aware containers - remain zip library territory (JSZip, fflate). The compression API's niche is exactly one continuous byte flow: a big JSON, a generated report, a serialized state snapshot. Within that niche it wins on being native, streaming and dependency-free.

When does client-side compression actually pay off?

When the bytes are large, compressible, and leave the client. The winning cases: exporting big JSON/table data (5-10x on structured data), saving application state snapshots to IndexedDB or cloud storage, and P2P or relayed data exchange. The losing cases: already-compressed data (JPEG, video, existing gzip - recompressing adds CPU and can grow size), tiny payloads (overhead dominates), and server uploads where the server re-compresses anyway. The check before building: sample the data, run it through CompressionStream, and compare sizes - the API makes that experiment four lines, which is the right way to justify or skip the feature.

Related tools