Compression

bext compresses responses automatically using Gzip, Brotli, or Zstd depending on what the client supports. Compression is enabled by default for text-based content types and runs inline with response delivery. For static assets, bext can serve pre-compressed files when available.

How It Works

When a request arrives, bext inspects the Accept-Encoding header to determine which compression algorithms the client supports. It then selects the best available algorithm using this priority order:

1. Zstd (zstd) -- best compression ratio at high speed, supported by modern browsers 2. Brotli (br) -- excellent compression ratio, widely supported 3. Gzip (gzip) -- universal compatibility, supported by every HTTP client

If the client does not advertise any supported encoding, the response is sent uncompressed. The Vary: Accept-Encoding header is always added to compressible responses so that CDNs and proxies cache each variant separately.

Configuration

Configure compression in the [compression] section of bext.config.toml:

[compression]
enabled = true
min_length = 1024          # Skip bodies smaller than 1KB
algorithms = ["zstd", "br", "gzip"]   # Priority order

[compression.gzip]
level = 6                  # 1 (fastest) to 9 (smallest)

[compression.brotli]
level = 4                  # 0 (fastest) to 11 (smallest)

[compression.zstd]
level = 3                  # 1 (fastest) to 22 (smallest)

Compression Levels

Each algorithm accepts a level that trades CPU time for compression ratio:

Algorithm Fast Balanced Max Default
Gzip 1 6 9 6
Brotli 0-1 4-5 11 4
Zstd 1 3 22 3

For a reverse proxy serving dynamic SSR content, lower levels are usually better: the CPU savings outweigh the marginal bandwidth improvement. The default levels provide a good balance for most workloads. For static assets that are compressed once and served many times, consider using pre-compressed files at higher levels.

Minimum Size Threshold

The min_length setting (in bytes) prevents bext from compressing tiny responses where the overhead of the compression frame exceeds the savings. The default is 1024 bytes (1 KB). A Gzip header alone is approximately 20 bytes, so compressing a 200-byte JSON response provides negligible benefit while consuming CPU.

[compression]
min_length = 256   # Compress anything over 256 bytes

Content-Type Filtering

By default, bext compresses responses with text-based MIME types:

- text/html

- text/css

- text/javascript / application/javascript

- application/json

- application/xml / text/xml

- image/svg+xml

- application/wasm

Binary content types are automatically excluded because they are already compressed or would not benefit from general-purpose compression:

- image/png, image/jpeg, image/webp, image/avif

- video/*, audio/*

- application/zip, application/gzip, application/x-rar

- application/octet-stream

- font/*

You can customize the compressible types list:

[compression]
types = [
  "text/html",
  "text/css",
  "text/javascript",
  "application/javascript",
  "application/json",
  "application/xml",
  "image/svg+xml",
  "application/wasm",
  "text/plain",
  "application/graphql-response+json",
]

Per-Route Compression Override

Route rules can override the server-wide compression mode for specific paths:

[[route_rules]]
pattern = "/api/stream/*"
compression = "off"       # Disable for SSE/streaming endpoints

[[route_rules]]
pattern = "/assets/*"
compression = "max"       # Maximum compression for static assets

The compression field accepts "fast", "balanced", "max", or "off".

Pre-Compressed Static Assets

For files in the static directory, bext looks for pre-compressed variants before compressing on the fly. If a client requests style.css and accepts Brotli, bext checks for style.css.br first. If found, it serves the pre-compressed file with zero CPU cost.

The lookup order matches the algorithm priority:

style.css.zst  ->  Content-Encoding: zstd
style.css.br   ->  Content-Encoding: br
style.css.gz   ->  Content-Encoding: gzip
style.css      ->  (compress on the fly or serve raw)

Generate pre-compressed files during your build step for maximum performance:

# In your build script
find dist/static -type f \( -name "*.js" -o -name "*.css" -o -name "*.html" -o -name "*.svg" \) \
  -exec brotli --best {} \; \
  -exec gzip --best --keep {} \;

Accept-Encoding Negotiation

bext implements proper content negotiation per RFC 7231. When the Accept-Encoding header includes quality values, bext respects them:

Accept-Encoding: br;q=1.0, gzip;q=0.8, zstd;q=0.5

In this example, the client prefers Brotli over Gzip over Zstd. bext selects the highest-quality algorithm that it supports and that is enabled in the configuration.

An explicit Accept-Encoding: identity or the absence of the header means the client does not want compressed responses. bext respects this and serves the raw body, but still adds Vary: Accept-Encoding so intermediate caches handle the variation correctly.

Streaming Compression

For chunked/streaming responses (SSE streams, large API responses), bext compresses each chunk as it is flushed rather than buffering the entire body. This preserves low time-to-first-byte while still reducing bandwidth. Streaming mode uses the same algorithm selection and level settings as buffered compression.