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.