Compression
serve compresses text-like responses unless compress: false (leave it to the proxy when one compresses).
serve compresses text-like responses unless compress: false (leave it to the proxy when one compresses). It picks the
coding from Accept-Encoding by q-value; a tie goes to brotli, then zstd, then gzip. zstd is offered only where
node:zlib has it at run time (Node 22.15 and later); on Node 22.12 the server negotiates brotli and gzip, with no
configuration. A streamed page is flushed after every chunk, text/event-stream is never compressed, and an answer that
already has a Content-Encoding, or a Cache-Control: no-transform, is left alone.
CompressOptions |
Default | Meaning |
|---|---|---|
minBytes |
1024 |
smallest response worth compressing, when its length is known |
level |
none | gzip and zstd level, 1 to 9 (gzipLevel overrides it for gzip); without it gzip uses 6 and zstd its own default |
brotliQuality |
4 |
brotli quality, 0 to 5 |
gzipLevel |
level, or 6 |
gzip level, 1 to 9 |
maxConcurrent |
256 |
most compressions running at once; further responses go out uncompressed |
types |
text, JSON, JavaScript, XML, SVG, WebAssembly | the content types worth compressing; the list you give replaces this one |
filter |
none | (request, response) => boolean; asked once for each response that would be compressed |
types takes media types ("application/x-custom"), families ("image/*") and regular expressions, all compared with
the parameters removed and the case ignored. filter must answer true to allow compression: false, any other value, a
promise and a thrown error all skip it (a throw is also reported to onError), so a mistake leaves a response
uncompressed, never the reverse. The options are checked when the server starts: an unknown name, a number out of range or
a types or filter of the wrong kind throws with the option's name.
BREACH. Compression can leak a secret that is reflected next to text an attacker controls: when a response contains
both (a CSRF token or session value, and a query parameter echoed back), the compressed size shows whether a guess was
right. Use filter to skip
compression for the responses that carry a secret, or serve those paths with Cache-Control: no-transform. Compression of
public content (scripts, styles, markup without secrets) is not affected.
import { serve } from "@shadow-js/dusk/node";
import type { Handler } from "@shadow-js/dusk/server";
declare const handler: Handler;
await serve(handler, {
compress: {
level: 5,
minBytes: 512,
// Keep these exact; add a family or a pattern when the app serves other text-like types.
types: ["text/html", "text/css", "text/javascript", "application/json", /\+json$/],
// The account pages reflect a token; skip compression for them.
filter: (request) => !new URL(request.url).pathname.startsWith("/account"),
},
});