Compression

bext compresses responses automatically using Brotli or Gzip depending on what the client supports. Compression is on by default and runs inline with response delivery. For static assets, bext can also serve pre-compressed files when they exist on disk.

How It Works

When a request arrives, bext inspects the Accept-Encoding header to determine which compression algorithms the client supports, and selects the best one:

  1. Brotli (br) -- preferred when the client advertises it; excellent compression ratio.
  2. Gzip (gzip) -- the fallback; universal compatibility.

If the client does not advertise either encoding (or sends Accept-Encoding: identity), the response is sent uncompressed. When compression is applied the Vary: Accept-Encoding header is added so that CDNs and proxies cache each variant separately.

The defaults are: gzip enabled, gzip level 6, a 256-byte minimum body length (min_length), and Vary: Accept-Encoding on. Brotli uses quality 4 (fast mode). These defaults are baked in (GzipConfig::default in crates/bext-server/src/ssr_pipeline/config.rs; brotli in crates/bext-core/src/compress/brotli.rs) — there is no [compression] section in bext.config.toml. Tuning is done either through nginx-compat directives (below) or per-route overrides.

Tuning Compression (nginx-compat mode)

In masquerade / nginx-compat mode, bext reads standard nginx gzip directives from the http {} block of your nginx config and maps them onto its own compression settings. This is the supported way to tune compression for nginx-compat sites:

http {
    gzip on;                    # enable/disable compression
    gzip_comp_level 6;          # gzip level, 1 (fastest) – 9 (smallest)
    gzip_min_length 256;        # skip bodies smaller than this many bytes
    gzip_vary on;               # add Vary: Accept-Encoding
    gzip_types text/html text/css application/javascript application/json image/svg+xml;
}

The directive mapping is implemented in crates/bext-nginx-compat/src/convert/gzip.rs. Only the directives shown above are honored: gzip, gzip_comp_level, gzip_min_length, gzip_vary, and gzip_types.

Compression Levels

The gzip level trades CPU time for compression ratio:

Level Profile
1 Fastest, lowest ratio
6 Balanced (default)
9 Smallest, highest CPU

For a reverse proxy serving dynamic SSR content, the default level 6 is a good balance. For static assets that are compressed once and served many times, prefer pre-compressed files at maximum effort (see below).

Minimum Size Threshold

gzip_min_length (in bytes) prevents bext from compressing tiny responses where the overhead of the compression frame exceeds the savings. The default is 256 bytes. A gzip header alone is roughly 20 bytes, so compressing a very small JSON response provides negligible benefit while consuming CPU.

Per-Route Compression Override

Route rules can override compression for specific paths via the compression field on a [[route_rules]] entry. The field accepts "off", "fast", "balanced", or "max". "off" is the load-bearing value — it forces identity encoding (no compression), which is the right choice for SSE / streaming endpoints. The other modes are accepted as hints and currently leave the negotiated encoding in place.

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

[[route_rules]]
pattern = "/assets/*"
compression = "max"       # Hint: prefer maximum compression for static assets

This is handled in crates/bext-server/src/handler.rs (the route_config.compression check); the field type lives on RouteRule in crates/bext-core/src/route/rules.rs.

Pre-Compressed Static Assets

For static files, bext looks for a pre-compressed neighbor file before compressing on the fly. If a client requests style.css and advertises Brotli, bext checks for style.css.br first; if it exists, it serves that file directly with the matching Content-Encoding and zero compression CPU cost.

The lookup order (when the client accepts the encoding) is .br > .zst > .gz:

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

Generate these neighbor files during your build step for maximum performance:

# In your build script — produce .br and .gz neighbors next to each asset
find dist/static -type f \( -name "*.js" -o -name "*.css" -o -name "*.html" -o -name "*.svg" \) \
  -exec brotli --best --keep {} \; \
  -exec gzip --best --keep {} \;

Accept-Encoding Negotiation

bext parses the Accept-Encoding header and prefers Brotli over Gzip when both are advertised:

Accept-Encoding: br, gzip

An explicit Accept-Encoding: identity or the absence of any supported encoding means the client does not want compressed responses. bext serves the raw body in that case.

Warning

SSE and streaming endpoints must set compression = "off" in their [[route_rules]] entry. Compression buffers the response body before writing, which defeats streaming and prevents clients from receiving events as they arrive.

Related