Build1 publisher2 min readPublished
zstd-stream compresses 4 GB files in the browser using about 35 MB of memory
zstd-stream, a JavaScript compression library, keeps browser memory near 35 MB for files of any size, according to its author. Because it streams, teams can compress multi-gigabyte files on the user's device, either to save them locally or to shrink them before upload.
The Engineer · Build desk

What happened
- The author tried pako, fflate, zstd-codec and @bokuweb/zstd-wasm, and each handled small files but ran the browser tab out of memory on big ones.
- By the author's account, most JavaScript libraries need 1.6 GB of RAM to compress a 1 GB file, and a 4 GB file crashes the tab.
- zstd-stream compiles Meta's open-source Zstandard C library to WebAssembly, and the streaming layer on top is the author's own code.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- decision Swapping pako for fflate, or one zstd port for another, will not stop large-file crashes. A team has to rewrite the call from compress(input) to stream piping.
- capability Files compressed in a user's browser come out as checksummed, standard .zst that the zstd command-line tool opens on a server, and corrupt or truncated input throws an error.
- cost The author says ingress is generally free on AWS, GCP and Azure, so the payoff from compressing before upload lands in user wait time, storage and egress.
- constraint Native CompressionStream does zstd only in Firefox 138+, without a level or progress setting, so archiving at level 19 still means shipping a library.
The 1.6 GB figure follows from the call shape. In a whole-file-in, whole-file-out API, the entire input and the entire output sit in memory at once, according to the library's author, writing on dev.to [4]. A browser tab can't hold an ArrayBuffer much past about 2 GB [5]. Four gigabytes of input is twice that ceiling before compression starts [1]. "No optimisation fixes that. The API shape is the problem," the author wrote [6].
Calling compressStream takes a ReadableStream and returns one. That is the same type File.stream(), fetch and Node's Readable.toWeb() already produce [8]. The same API runs in Node.js 18 and later [21]. I think the interface choice is the best engineering in the package, because it connects to the file picker, the network and Node's file system without adapter code [8]. Data moves through fixed buffers 128 KB at a time, so a 4 MB file and a 4 GB file use the same memory [9]. When the consumer is slow, the library stops reading the source instead of piling data up in RAM. pipeTo carries that backpressure through, so compression waits for a slow disk [10].
The package embeds its WebAssembly binary, with no .wasm file to host and no bundler plugin to configure. It installs at 163 kB with zero dependencies [12]. The example that writes to disk imports one anyway: StreamSaver, to receive the piped output [13]. The 35 MB figure [2] holds end to end only if whatever reads the output also streams. An app that gathers the stream back into a single buffer puts the whole output in memory again. Holding the whole output was half of what broke the older libraries [4].
At the author's measured ratios, a 1,000 MB file shrinks to about 182 MB with zstd and about 286 MB with gzip [2]. At 147 MB/s that file takes about 6.8 seconds, in line with the author's figure of roughly 7 [3]. The post's text does not state the hardware or the compression level behind the comparison. The test data was log-style. For the 5.5x ratio to carry over, a team's files have to look like those logs [14].
Upload is the slowest part of the trip, the author wrote [15]. In Chrome and Edge, the compressed stream can go straight into fetch with a `Content-Encoding: zstd` header and `duplex: "half"`, provided the server runs HTTP/2 or later [18]. Firefox and Safari don't support streaming request bodies yet [19]. A single streamed POST covers two of the four browsers the author tested [4].
What to watch
- Chrome, Edge or Safari adding zstd to CompressionStream, and whether any engine exposes a compression level or progress events.
- Independent memory and throughput measurements of zstd-stream on data other than log text, with the hardware stated.
- Firefox and Safari shipping streaming request bodies in fetch, so one streamed upload path covers all four browsers the author tested.