Drupal
Drupal Brotli compression on production sites in 2026
Drupal Brotli compression in core 11.4 is the first time I can shrink aggregated CSS and JavaScript on a museum or regulator site without bolting on AdvAgg for static encodings alone. I am Alan Vo, a Gold Coast web developer with 18 years shipping Drupal catalogues, and I treat Drupal Brotli compression as an infrastructure plus core upgrade task, not a checkbox in the Performance UI. When aggregation is already on, 11.4 can write .br siblings beside .gz files under sites/default/files, and modern browsers pick the smaller payload automatically if the web server is configured to serve it. This note is what I run after a Drupal 11.4.x deploy on stacks that already aggregate assets in production.
Readers are lead developers and sysadmins on Drupal 11.3.x or 11.4.x who host on nginx or Apache behind a CDN. It pairs with Drupal HTMX on production catalogues because both wins land in the same 11.4 maintenance window, and with Drupal JSON:API on live catalogues because JSON responses are unaffected while theme asset weight still dominates first paint on hybrid pages. Facts below come from the Brotli change record, the Drupal 11.4.0 announcement, and the aggregation guide.
Why Drupal Brotli compression matters after 11.4.0
Drupal Brotli compression matters after 11.4.0 because core now pre-compresses aggregated theme and module assets at dump time instead of leaving every byte to the origin gzip module alone. The change record cites typical 15-25% smaller files than gzip for the same CSS and JS aggregates, which is consistent with what Brotli achieves on text assets elsewhere on the web. That is not a Largest Contentful Paint guarantee on its own, but on catalogue sites where a single page still ships hundreds of kilobytes of aggregated admin-adjacent and public libraries, shaving a fifth off static weight reduces transfer time on mobile networks editors and visitors actually use.
Drupal unifies the old system.performance.css.gzip and system.performance.js.gzip flags into css.compress and js.compress. When compression is enabled and the PHP ext-brotli extension is present, AssetDumper writes both .gz and .br variants. Apache sites pick up updated .htaccess rules that prefer Brotli when the client advertises support. Nginx does not get that for free: you must add gzip_static and brotli_static with a try_files chain on the sites/.../files/css and js paths, as documented in the change record. I plan nginx edits in the same change request as the core bump so QA does not falsely conclude Brotli is broken.
The feature only applies to aggregated files Drupal writes to the public files directory. It does not replace dynamic Content-Encoding on HTML, and it does not compress arbitrary uploaded media. Decoupled front ends that skip Drupal aggregates still need their own pipeline. For the majority of my Drupal catalogue themes, aggregates remain the bulk of JavaScript and CSS weight, so this is worth the ops attention.
How Drupal Brotli compression works in core
Drupal Brotli compression works at aggregate generation time through the asset dumper, not at request time inside PHP-FPM. When css.preprocess and js.preprocess are true and css.compress / js.compress are true, Drupal hashes library definitions into stable filenames, writes the raw .css or .js, then emits compressed derivatives if extensions exist. Gzip continues for older clients; Brotli is preferred where both are offered and the server configuration maps Accept-Encoding to the .br file.
The 11.4.0 release post states the dependency explicitly: without ext-brotli on PHP, behavior reverts to gzip-only generation even after upgrade. I verify with php -m | grep -i brotli on each app container image before enabling compression in production config exports.
Settings migration is automatic via system_post_update_migrate_compress_setting(). Sites that had gzip enabled keep compression enabled under the new keys. Sites that disabled gzip stay off until someone turns compress on deliberately. Module authors who set css.gzip in install hooks must update to css.compress, as noted in the change record, or their overrides silently miss Brotli.
On Apache, the committed .htaccess fragment negotiates Brotli ahead of gzip for aggregate paths. On nginx, the change record example uses separate location blocks for css and js under sites/*/files/, with try_files $uri.br $uri.gz $uri =404. The ngx_brotli module supplies brotli_static; it is not bundled in every distribution package, so I confirm the module is loaded in staging before promoting config.
CDN behavior is the third leg. Some providers recompress origin responses and ignore precompressed static files unless you disable that feature for aggregate paths or origin-pull the .br object directly. I test one aggregate URL with curl -H 'Accept-Encoding: br' -I through the public hostname, not only loopback, because a misconfigured edge strips the benefit while logs still show 200 responses.
Drupal Brotli compression production checklist
A Drupal Brotli compression production checklist starts with core version and PHP extensions, because everything else is wasted if aggregates never gain a .br file on disk.
- Upgrade path. Target Drupal 11.4.0 or newer on supported 11.4.x security releases. Run database updates so the compress setting migration executes once.
- Aggregation on. Confirm
css.preprocessandjs.preprocessare true insystem.performance. The Performance administration screen labels these as aggregate options; compression without aggregation has nothing to compress. - Compression flags. Ensure
css.compressandjs.compressare true. If config is environment-split, set them insettings.phpon production only, mirroring patterns from the caching documentation for preprocess overrides. - PHP Brotli. Install and enable
ext-brotlion every PHP worker that runs web requests and drush cache rebuilds that regenerate aggregates. - Rebuild aggregates. After deploy, run a full cache rebuild so
AssetDumperrewrites css and js undersites/default/files. I delete stale aggregates only when checksum mismatches appear, not on every deploy. - Web server static mapping. Apply nginx
brotli_staticrules from the change record or rely on Apache.htaccessupdates shipped with core. Reload the web tier and spot-check file existence on disk (*.css.br). - CDN and cache headers. Align long-cache headers on aggregate paths with immutable fingerprints already in filenames. Avoid purging entire zones when only aggregates changed.
- Staging parity. Match production PHP modules and nginx modules on staging so developers do not assume Brotli works based on local Docker images that lack
ext-brotli. - Rollback plan. Document how to set
css.compressandjs.compressfalse and rebuild if a rare library breaks under aggregation, independent of Brotli. - Monitor transfer size. Capture median aggregate byte size gzip versus Brotli on three representative catalogue templates before and after.
What breaks when Drupal Brotli compression is misconfigured
What breaks when Drupal Brotli compression is misconfigured is usually silent fallback to larger gzip files or 404s on aggregate URLs, not a PHP stack trace. Missing ext-brotli means no .br files appear; the site still works, but you do not get the marketed win and may wonder why Lighthouse transfer size barely moved.
Nginx without brotli_static serves the uncompressed .css even when .br exists on disk, which inflates responses for every browser. The fix is server config, not another drush cron run. Conversely, enabling brotli_static without installing ngx_brotli prevents nginx from starting, which is why I validate config with nginx -t in CI for infrastructure repos.
Wrong try_files ordering or overly broad location regexes can send HTML error pages with text/css types, breaking styles site-wide. I keep css and js locations scoped to sites/.*/files/(css|js)/ as in the change record rather than reusing a single catch-all static block that also catches private stream wrappers.
Developers who still set deprecated css.gzip in custom install code may leave compression off after upgrade if their hook runs after the migrator and resets config. Grep the codebase for gzip keys before deploy.
Asset aggregation itself can surface JavaScript redeclaration bugs when a contrib library assumes it loads alone. Brotli does not cause that, but enabling compression often coincides with turning aggregation on in lower environments. The aggregation guide documents opting a library out with preprocess: false in libraries.yml when needed.
Multi-site installs must confirm each site’s files directory receives regenerated aggregates. A shared container image with Brotli but a read-only files mount on one vhost produces inconsistent behavior across hostnames on the same codebase.
How to measure Drupal Brotli compression on live catalogues
How to measure Drupal Brotli compression on live catalogues combines on-disk checks, response headers, and field metrics tied to template types. I pick one hashed css URL and one js URL from view-source on a heavy exhibition listing or property search page, then request them twice: with Accept-Encoding: gzip and with Accept-Encoding: br. Content-Length should drop on Brotli for the same logical file. If lengths match, the edge or origin is not serving .br.
WebPageTest or Lighthouse transfer size on mobile throttling gives a page-level read after Brotli is live. I compare the same URL before and after on staging with identical content, because production editor activity shifts payloads. Drupal.org quotes 15-25% better compression ratios; I log actual percentages per theme on client reports without treating vendor figures as my portfolio analytics.
Server-side, watch PHP worker CPU during the first cache rebuild after enablement. Compression at dump time spikes CPU briefly; it should not repeat on every request. If CPU stays high, something may be regenerating aggregates continuously, which points to cache misconfiguration rather than Brotli specifically.
For collections with many languages, aggregate filenames still dedupe by library definition, but different enabled modules per language domain can change counts. Measure per domain when Drupal multisite or domain modules split features.
Editor experience rarely changes when only static aggregates compress. I still run smoke tests on admin themes after deploy because Gin and Claro both ship sizable CSS stacks that flow through the same dumper.
Related Drupal catalogue work on alanvo.com
Related Drupal catalogue work on this site includes Royal Ontario Museum, where collection discovery pages mix rich media with searchable indexes, and NDIS Commission, where accessibility and predictable performance matter as much as marketing polish. Immobel property platform is another stack where aggregated front-end assets and CDN caching decisions stack together. Those projects informed how I sequence core upgrades, PHP extension installs, and nginx edits so Drupal Brotli compression actually shows up in transfer metrics instead of only in release notes.
FAQ
Does Drupal Brotli compression replace CDN gzip?
Drupal Brotli compression complements CDN gzip rather than replacing it. Core writes precompressed .br and .gz files on the origin for aggregated CSS and JS. The CDN or browser still negotiates encoding via Accept-Encoding. You want the edge to serve the .br object when supported so the origin does not compress on the fly repeatedly. HTML and uncached responses may still use dynamic Brotli or gzip at the CDN layer separately from this Drupal feature.
Where do I enable Drupal Brotli compression in the admin UI?
Drupal Brotli compression does not have its own checkbox on the Performance form. You enable CSS and JavaScript aggregation under Configuration then Development then Performance, then ensure css.compress and js.compress are true in configuration, often via config export or settings.php because the gzip toggles moved out of the UI into unified compress flags in 11.4. Without aggregation preprocess enabled, compress has no aggregate files to process.
Which PHP and nginx pieces does Drupal Brotli compression need?
Drupal Brotli compression needs the PHP ext-brotli extension on application servers so AssetDumper can write .br files. Apache sites use updated core .htaccess rules. Nginx sites need the ngx_brotli module and brotli_static on in location blocks for sites/.../files/css and js, with try_files preferring $uri.br before $uri.gz, as documented on the change record. Missing either PHP or nginx pieces leaves gzip fallback or uncompressed static files.
Should I upgrade to Drupal 11.4 only for Brotli on aggregates?
You should upgrade to Drupal 11.4 when your security and support window allows, not in isolation for Brotli alone. The same release line ships entity loading and query improvements that matter on JSON:API heavy catalogues, which often matters more than static bytes. If you are already planning 11.4 for those reasons, schedule Brotli verification in the same deploy window so PHP extensions and nginx edits are tested once.
Keep reading
Contact if you want this kind of work on a live store.