CompressionStream for browser-side gzip
Published August 10, 2027
export const gzipEncode = async (text: string): Promise<Uint8Array> => {
const stream = new Blob([text]).stream().pipeThrough(new CompressionStream("gzip"));
const buffer = await new Response(stream).arrayBuffer();
return new Uint8Array(buffer);
};
export const gzipDecode = async (bytes: Uint8Array): Promise<string> => {
// Uint8Array's .buffer is typed ArrayBufferLike (it could be a SharedArrayBuffer),
// which BlobPart doesn't accept -- a real runtime non-issue, just a strict-mode gap.
const stream = new Blob([bytes as unknown as BlobPart]).stream().pipeThrough(new DecompressionStream("gzip"));
const buffer = await new Response(stream).arrayBuffer();
return new TextDecoder().decode(buffer);
};What
gzipEncode/gzipDecode compress and decompress bytes using CompressionStream/DecompressionStream — the browser's built-in gzip implementation, exposed as a pair of TransformStream-shaped objects.
Why it matters
Compressing data client-side used to mean shipping a library — pako or fflate compiled from zlib — just to shrink a payload before a fetch, or to gzip a blob before writing it to IndexedDB or OPFS. CompressionStream gets you the same DEFLATE/gzip codec the browser already ships for decoding Content-Encoding: gzip responses, with zero bytes of JavaScript downloaded and no WASM to instantiate. It's a stream, so it composes with everything else in the Streams ecosystem via pipeThrough — chain it after a ReadableStream of file chunks, or in front of a fetch request body, without buffering the whole payload in memory first.
How it works
gzipEncodewraps the input string in aBlobto get aReadableStreamcheaply (Blob.stream()), then pipes it throughnew CompressionStream("gzip")— the transform stream reads uncompressed chunks in and emits gzip-compressed chunks out.new Response(stream).arrayBuffer()is the drain:Responseaccepts anyReadableStreamas a body and itsarrayBuffer()method collects every chunk into one contiguous buffer, which sidesteps writing a manual chunk-collection loop.gzipDecodeis the mirror image withDecompressionStream("gzip"), ending inTextDecoder().decode()to turn the recovered bytes back into a string.- Both directions go through the same
Blob→ stream →Response→ buffer path, just with the transform swapped — that symmetry is the whole API surface.
Gotchas
- The format string accepts
"gzip","deflate"(zlib-wrapped DEFLATE), or"deflate-raw"(headerless DEFLATE) — picking the wrong one to match data compressed elsewhere (e.g. a server using raw DEFLATE) produces a decode error, not a silent mismatch. - This is a general-purpose codec, not a file format library — there's no gzip header manipulation, no multi-member stream handling, and no equivalent to
zlib's dictionary support. For anything beyond "compress these bytes, decompress these bytes," you still need a real library. - Full cross-browser support landed later than the Chrome-only headline date suggests: Chrome/Edge 80, Safari 16.4, and Firefox only as of version 113 — check your minimum supported Firefox before relying on this without a fallback.
- Compressing something already compressed (a JPEG, a zip) typically makes it larger, not smaller — gzip works well on text (JSON, HTML) and poorly on already-entropic binary data.
Related
TransformStream— the general building blockCompressionStreamis shaped like; you can write your own for other codecs the platform doesn't ship.fetchrequest/response streaming — pairs naturally with this: compress a request body stream before sending, or decompress a response stream as it arrives, without buffering either end.