DEV Community

Cover image for Serving an Astro Static Site with Brotli and Gzip on Nginx: A Complete, Practical Guide
Athreya aka Maneshwar
Athreya aka Maneshwar

Posted on

Serving an Astro Static Site with Brotli and Gzip on Nginx: A Complete, Practical Guide

Hello, I'm Maneshwar. I'm working on FreeDevTools online currently building *one place for all dev tools, cheat codes, and TLDRs* — a free, open-source hub where developers can quickly find and use tools without any hassle of searching all over the internet.

Modern frontend frameworks like Astro produce highly optimized static sites, but the real performance gain comes from serving those assets with the right compression strategy.

Brotli, Gzip, and long-term caching dramatically reduce transfer size and improve Lighthouse, Core Web Vitals, and TTFB metrics.

This guide walks through Astro’s static compressor output, installing Brotli support in Nginx correctly, configuring Brotli + Gzip fallback, and verifying actual compression behavior via DevTools and curl.

1. Astro’s Static Compression Artifacts

Astro’s compressor integration can automatically generate precompressed versions of every HTML, CSS, and JS file:

// astro.config.mjs
export default defineConfig({
  compressor({ gzip: { level: 9 }, brotli: true }),
});
Enter fullscreen mode Exit fullscreen mode

Astro produces files like:

File Compression Browser Support Purpose
.html.br Brotli Yes (all modern browsers + Googlebot) Primary asset (smallest size)
.html.gz Gzip Yes (universal) Fallback for older devices/crawlers
.html.zst Zstandard No (not supported by browsers yet) Future-proof but ignored today
.html Raw Yes Final fallback

A typical example:

index.html       84 KB
index.html.br    24 KB
index.html.gz    28 KB
index.html.zst   29 KB
Enter fullscreen mode Exit fullscreen mode

Brotli typically saves ~60–80% relative to raw HTML.

2. Why You Should Use Static Brotli + Gzip Fallback

Browsers tell servers which compression formats they accept via:

Accept-Encoding: gzip, deflate, br, zstd
Enter fullscreen mode Exit fullscreen mode

Nginx must honor this content negotiation. The correct behavior is:

  1. Serve Brotli (.br) if supported
  2. Else serve gzip (.gz)
  3. Else serve the raw file

Nginx will not do this automatically—you must enable:

  • brotli_static (for .br files)
  • gzip_static (for .gz files)
  • try_files to choose between files in the correct order

3. Installing Nginx with Brotli Support

Ubuntu’s default nginx and nginx-extras do not include Brotli anymore.
This is why the directive:

brotli on;
Enter fullscreen mode Exit fullscreen mode

produces:

unknown directive "brotli"
Enter fullscreen mode Exit fullscreen mode

To enable Brotli, you must install the official Nginx build plus the dynamic Brotli module package:

sudo apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring
curl https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx.gpg
echo "deb [signed-by=/usr/share/keyrings/nginx.gpg] http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list
sudo apt update
sudo apt install nginx libnginx-mod-brotli
Enter fullscreen mode Exit fullscreen mode

If libnginx-mod-brotli installs successfully, you will have:

/usr/lib/nginx/modules/ngx_http_brotli_filter_module.so
/usr/lib/nginx/modules/ngx_http_brotli_static_module.so
Enter fullscreen mode Exit fullscreen mode

Enable them:

load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
Enter fullscreen mode Exit fullscreen mode

At this point, Brotli directives become available.

4. Correct Nginx Configuration for Astro Brotli + Gzip

Assume your Astro build output is located in /tools/.
This block correctly enables static Brotli + static Gzip + fallback:

location ^~ /freedevtools/ {
    alias /tools/;

    # Long-term caching
    expires 1y;
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header X-Content-Type-Options "nosniff";

    # Brotli static
    brotli on;
    brotli_static on;
    brotli_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        application/xml+rss
        image/svg+xml
        text/html;

    # Gzip fallback
    gzip on;
    gzip_static on;
    gzip_comp_level 6;
    gzip_min_length 1024;
    gzip_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        image/svg+xml
        text/html;

    # Resolution order: br → gz → raw
    try_files $uri$br $uri$gz $uri =404;
}
Enter fullscreen mode Exit fullscreen mode

Important part:

try_files $uri$br $uri$gz $uri;
Enter fullscreen mode Exit fullscreen mode

Nginx evaluates files in this order:

  1. index.html.br
  2. index.html.gz
  3. index.html

Exactly how CDNs like Vercel, Netlify, Cloudflare Pages behave.

5. Verifying Brotli/Gzip in Browser DevTools

Open Chrome DevTools → Network → select a resource.

Request Headers

You should see something like:

Accept-Encoding: gzip, deflate, br, zstd
Enter fullscreen mode Exit fullscreen mode

If br is present, the browser supports Brotli.

Response Headers

Brotli response:

Content-Encoding: br
Enter fullscreen mode Exit fullscreen mode

Gzip response:

Content-Encoding: gzip
Enter fullscreen mode Exit fullscreen mode

Raw response:

No Content-Encoding header.

If HTML/CSS/JS files show Content-Encoding: br, your server is correctly serving Brotli.

6. Verifying with curl (Most Reliable Method)

Force Brotli:

curl -I -H "Accept-Encoding: br" https://your-domain/path/
Enter fullscreen mode Exit fullscreen mode

Expected:

Content-Encoding: br
Enter fullscreen mode Exit fullscreen mode

Force Gzip:

curl -I -H "Accept-Encoding: gzip" https://your-domain/path/
Enter fullscreen mode Exit fullscreen mode

Expected:

Content-Encoding: gzip
Enter fullscreen mode Exit fullscreen mode

Force no compression:

curl -I -H "Accept-Encoding: identity" https://your-domain/path/
Enter fullscreen mode Exit fullscreen mode

Expected:

(no Content-Encoding)
Enter fullscreen mode Exit fullscreen mode

Full client preference:

curl -I -H "Accept-Encoding: br, gzip, zstd" https://your-domain/path/
Enter fullscreen mode Exit fullscreen mode

Expected:

Content-Encoding: br
Enter fullscreen mode Exit fullscreen mode

7. Cloudflare Behavior

If Cloudflare sits between the client and your Nginx origin:

  • Cloudflare supports Brotli compression natively.
  • If Nginx serves .br, Cloudflare will cache the Brotli file.
  • If Nginx does NOT support Brotli, Cloudflare will still send Brotli (recompressing on the edge).
  • Googlebot fully supports Brotli.

So your site will serve Brotli even if your Nginx origin returns gzip or raw.

Conclusion

To serve Astro static sites with the highest performance:

  1. Astro generates .br, .gz, .zst, and raw files.
  2. You must install an Nginx build that supports Brotli (libnginx-mod-brotli or custom build).
  3. Use this resolution order:
   try_files $uri$br $uri$gz $uri;
Enter fullscreen mode Exit fullscreen mode
  1. Verify compression using browser DevTools and curl. When configured correctly:
  2. Brotli is served to all modern browsers.
  3. Gzip covers older clients.
  4. Everything else receives raw files.
  5. Cloudflare continues to serve Brotli regardless of origin behavior.

This combination yields maximum performance, minimal bandwidth, and best practice SEO compliance for Astro deployments.

FreeDevTools

I’ve been building for FreeDevTools.

A collection of UI/UX-focused tools crafted to simplify workflows, save time, and reduce friction in searching tools/materials.

Any feedback or contributors are welcome!

It’s online, open-source, and ready for anyone to use.

👉 Check it out: FreeDevTools
⭐ Star it on GitHub: freedevtools

Top comments (0)