[]previous and next page

Compression

serve compresses text-like responses unless compress: false (leave it to the proxy when one compresses).

For agentsUpdated

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.

compression.tsts
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"),
  },
});