Fewer Bytes on the Wire: Brotli, Minification and a Year of Browser Cache

Cached pages are now stored pre-compressed with Brotli, JS and CSS bundles are minified according to what they actually are, and assetUrl() earns your assets a year of immutable browser cache without ever serving a stale bundle.

Brotli, computed once

Everything outside the API area is compressed - published site content and portal assets alike. The interesting part is where.

A cached page is not compressed per request. When the cache entry is written, Brotli and gzip variants are written alongside it, off-thread, at Brotli quality 11 - the slowest and smallest setting there is. Every later hit serves a file that is already compressed. The expensive setting gets paid once and reused, instead of a cheaper setting being paid on every single request.

Files below 1 KB or above 8 MB skip the variants and are handled by the compression middleware instead.

Your site's own API is deliberately left out. yoursite.com/API/orders runs #/API/orders.js, is forced to application/json, and is never cached - so compressing it would mean paying CPU on every call, and it would drop Content-Length from the API log.

Minification follows the real MIME type

Minification runs when a page is rendered, never on a cache hit, and only when caching is enabled and the response is actually going into the cache.

Which minifier runs is decided by what the content genuinely is:

Content

Without a minify block

With minify.enabled

text/html

Whitespace compression

html: true -> chosen provider

text/css

Untouched

css: true -> NUglify

JavaScript

Untouched

js: true -> NUglify

JSON, XML, plain text

Untouched

Untouched

Watch the html flag. Inside a minify block, js and css default to true but html defaults to false. So {"minify": {"enabled": true}} turns JS and CSS minification on and HTML whitespace compression off - probably not what you meant. Set all three explicitly.

{
  "cache":  { "enabled": true },
  "minify": { "enabled": true, "html": true, "js": true, "css": true }
}

A bundle that the minifier cannot parse is served in its original form and the problem is logged. Half-minified JavaScript breaks a page; the original never does.

JS and CSS bundles are written as .hash files that declare their own MIME type, and are referenced without that extension:

// /assets/js/bundle.js.hash - referenced as /assets/js/bundle.js
docly.setMime("text/javascript");

They are cached, pre-compressed and minified on exactly the same terms as HTML.

A year of browser cache, without stale bundles

Server-side invalidation has never been the hard part - publishing bumps the site's timestamp. The browser is the hard part: the URL has not changed, so it has no reason to fetch anything.

assetUrl() stamps your own relative asset URLs with the site's last-modified time:

<script src="#assetUrl('/assets/js/bundle.js')#"></script>
<!-- -> /assets/js/bundle.js?v=20260714183000 -->

External and protocol-relative URLs come back untouched - we don't know when someone else's CDN last changed.

The stamp is only half of it. The other half is the cache policy that recognises it:

Response

Cache-Control

Versioned asset (?v=)

max-age=31536000, immutable - a year, never revalidated

Other static files

max-age=31536000

Public HTML without a version

no-cache, must-revalidate

A versioned URL can be frozen for a year precisely because a new publish produces a new URL. And public HTML revalidates every time, so the moment you publish, visitors pick up the new ?v= references immediately. Docly answers those revalidations with a 304 and an empty body, so they cost almost nothing.

That combination - immutable assets behind a versioned URL, always-fresh HTML pointing at them - is exactly what Lighthouse's "efficient cache policy" audit is looking for.

Templates that build faster

One more thing landed on the same theme, further down in the runtime. A method call chain used to resolve its receiver more than once, and in a chain each link multiplied the work beneath it.

The templates on one of our own sites decode HTML entities using eleven chained .split().join() pairs inside a single expression. Page generation on a cache miss went from 15-29 seconds to under 0.3.

Check it worked

curl -sI -H "Accept-Encoding: br" https://yoursite.com/ | grep -iE "content-encoding|cache-control"

You want content-encoding: br on the page, and max-age=31536000, immutable on an asset URL that carries ?v=.