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:
- Brotli (
br) -- preferred when the client advertises it; excellent compression ratio. - 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.
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
- Routing —
[[route_rules]]syntax used for per-route compression overrides - HTTP/3 & QUIC — protocol layer that compression sits beneath; QUIC has its own flow-control
- Guides: WebSockets — SSE endpoints that require
compression = "off" - Configuration: Rust CSS Engine — static asset pipeline that generates pre-compressed CSS outputs
- Architecture: Request Lifecycle — where compression fits in the bext handler chain