From 89c92cb3882d7010e0206d08fddb7754e4e660bb Mon Sep 17 00:00:00 2001 From: Pierre Baguet Date: Fri, 23 Oct 2026 16:15:17 +0200 Subject: [PATCH 1/2] docs(#296): serve precompressed theme assets, enable HTTP/2, and minify JavaScript when needed --- docs/getting-started/apache-configuration.md | 76 +++++++++++ docs/getting-started/cdn.md | 7 +- docs/getting-started/nginx-configuration.md | 124 ++++++++++++++++++ docs/upgrading/update.md | 6 + .../getting-started/apache-configuration.md | 76 +++++++++++ .../version-3.2/getting-started/cdn.md | 7 +- .../getting-started/nginx-configuration.md | 124 ++++++++++++++++++ .../version-3.2/upgrading/update.md | 6 + 8 files changed, 422 insertions(+), 4 deletions(-) diff --git a/docs/getting-started/apache-configuration.md b/docs/getting-started/apache-configuration.md index 0492d1bbc..d848b07b6 100644 --- a/docs/getting-started/apache-configuration.md +++ b/docs/getting-started/apache-configuration.md @@ -43,6 +43,82 @@ the class that writes it. An `.htaccess` already present in the directory is lef these headers to it yourself. When `document_cache_dir_from_web_root` moves the document cache, the file goes in that directory. +## Theme assets + +`php Thelia asset-map:compile` writes the theme files to `public/assets/` with a digest in +their names. In production it also writes a `.br`, a `.zst` and a `.gz` copy next to each text +file (see [Precompressed assets](./nginx-configuration.md#precompressed-assets)). From Thelia 3.3, +the `public/.htaccess` of the shop serves the `.br` and `.gz` copies when the browser accepts +them (it needs `mod_rewrite` and `mod_headers`; without `mod_headers` the original file is sent). +On an older `.htaccess`, add these rules to it. If the virtual host does not read `.htaccess` +files (`AllowOverride None`), put them in the `` block of `public/` instead: while +the `.htaccess` is read, its rewrite rules replace those of the `` block, which are +then ignored. + +```apache + + + RewriteEngine On + + # Brotli, if the browser accepts it and the original file exists + RewriteCond %{HTTP:Accept-Encoding} br + RewriteCond %{REQUEST_FILENAME} -f + RewriteCond %{REQUEST_FILENAME}.br -s + RewriteRule ^(assets/.*)\.(css|js|json|svg)$ $1.$2.br [QSA] + + # Gzip, if the browser accepts it and the original file exists + RewriteCond %{HTTP:Accept-Encoding} gzip + RewriteCond %{REQUEST_FILENAME} -f + RewriteCond %{REQUEST_FILENAME}.gz -s + RewriteRule ^(assets/.*)\.(css|js|json|svg)$ $1.$2.gz [QSA] + + # Right content type, and no second compression by mod_brotli or mod_deflate + RewriteRule \.css\.(br|gz)$ - [T=text/css,E=no-brotli:1,E=no-gzip:1] + RewriteRule \.js\.(br|gz)$ - [T=text/javascript,E=no-brotli:1,E=no-gzip:1] + RewriteRule \.json\.(br|gz)$ - [T=application/json,E=no-brotli:1,E=no-gzip:1] + RewriteRule \.svg\.(br|gz)$ - [T=image/svg+xml,E=no-brotli:1,E=no-gzip:1] + + + Header append Content-Encoding br + Header append Vary Accept-Encoding + + + Header append Content-Encoding gzip + Header append Vary Accept-Encoding + + + +``` + +The rules follow the precompressed content pattern of the Apache manual +([mod_brotli](https://httpd.apache.org/docs/2.4/mod/mod_brotli.html#precompressed) and +[mod_deflate](https://httpd.apache.org/docs/2.4/mod/mod_deflate.html#precompressed)). Two +details differ: + +- The `public/.htaccess` of Thelia already compresses text on the fly with `mod_deflate` and + `mod_brotli`. `E=no-brotli:1,E=no-gzip:1` stops them from compressing a file that is already + compressed. +- `RewriteCond %{REQUEST_FILENAME} -f` requires the original file to exist, so a compressed copy + is never served for a path whose original was removed. + +The rules only apply under `assets/`. Thelia's `.htaccess` sends every other request that is not +a file to `index.php`, and it does not touch a file that exists. + +## HTTP/2 + +The Flexy theme serves one JavaScript file per module through an import map: the home page +references about 80 module URLs and 15 `modulepreload` links. Over HTTP/1.1 these requests queue +on six connections per host. A server without HTTP/2 slows this model down noticeably, so enable +it on the HTTPS vhost: + +```apache +Protocols h2 http/1.1 +``` + +This needs `mod_http2` (`a2enmod http2` on Debian) and an MPM that supports it: `mpm_event` or +`mpm_worker`, not `mpm_prefork`. With `mpm_prefork`, switch to `mpm_event` and run PHP through +PHP-FPM (`proxy_fcgi`). Browsers only use HTTP/2 over TLS, so the shop needs HTTPS. + ## Required writable directories Apache needs write access to these directories: diff --git a/docs/getting-started/cdn.md b/docs/getting-started/cdn.md index 96ae41b45..1bec77d16 100644 --- a/docs/getting-started/cdn.md +++ b/docs/getting-started/cdn.md @@ -22,7 +22,9 @@ URLs do not change, the back office works as before, and Thelia needs three sett ### 1. Cache headers on the origin -Add to the shop's server block (see the [nginx page](./nginx-configuration.md)): +Add to the shop's server block (see the [nginx page](./nginx-configuration.md), which extends +the `/assets/` location below with `gzip_static` and `brotli_static`: keep a single `/assets/` +location): ```nginx # Theme files: the name carries a digest of the content. @@ -131,7 +133,8 @@ the shop domain. - A deploy needs no purge: a changed theme file gets a new name. Run `php Thelia asset-map:compile` on each deploy (`bin/install` does not), again after `thelia:cache:clear` (it empties `public/assets` unless `--without-assets`), and - `importmap:install` after updating the theme package. + `importmap:install` after updating the theme package. In production, this command also + writes the minified CSS and the precompressed `.br`, `.zst` and `.gz` files. - Never set `liip_imagine.webp.generate: true`: every image then goes through a private `302` that no CDN caches. To check, the second load of a product page must show no `/media/cache/resolve/` in `src` or `srcset` (the zoom link `data-zoom-src` may keep one): diff --git a/docs/getting-started/nginx-configuration.md b/docs/getting-started/nginx-configuration.md index 4908102f9..e9fc75a3e 100644 --- a/docs/getting-started/nginx-configuration.md +++ b/docs/getting-started/nginx-configuration.md @@ -98,6 +98,130 @@ not read it, so a shop served by nginx needs this block. It is required from The 3.1.2 on: add it to the server block of an existing shop when you update. If `document_cache_dir_from_web_root` moves the document cache, change the path of the location. +## Theme assets + +`php Thelia asset-map:compile` writes the theme files to `public/assets/` with a digest in +their names. In production it also writes a `.br`, a `.zst` and a `.gz` copy next to each text +file (see [Precompressed assets](#precompressed-assets) below). Serve them with a dedicated +location: + +```nginx +# Theme files: the name carries a digest of the content. +location ^~ /assets/ { + gzip_static on; + brotli_static on; # needs the ngx_brotli module, remove the line without it + gzip_vary on; + + add_header Cache-Control "public, max-age=31536000, immutable" always; + add_header Access-Control-Allow-Origin "*" always; + + # The original file must exist: gzip_static and brotli_static do not check it + try_files $uri =404; +} +``` + +The `^~` prefix makes this location win over the static files location above, so the cache +headers are repeated inside it. This is the same `/assets/` location as in +[Serving files from a CDN](./cdn.md): keep a single one in the server block. + +`gzip_static` is part of the standard nginx build on Debian. `brotli_static` comes from the +ngx_brotli module, packaged as `libnginx-mod-http-brotli-static` on Debian. nginx +does not serve `.zst` files natively: those are for servers and proxies that support them. + +`gzip_static on` and `brotli_static on` answer with `$uri.gz` or `$uri.br` when the browser +accepts the encoding, and they do so without checking that `$uri` exists. Without +`try_files $uri =404`, a request for a path whose original file was removed would still receive +its stale compressed copy. With it, a missing original is a 404 and no compressed file is ever +served alone. `gzip_vary on` adds `Vary: Accept-Encoding`, so a cache keeps the compressed and +the plain versions apart. + +## HTTP/2 + +The Flexy theme serves one JavaScript file per module through an import map: the home page +references about 80 module URLs and 15 `modulepreload` links. Over HTTP/1.1 these requests queue +on six connections per host. A server without HTTP/2 slows this model down noticeably, so enable +it on the TLS listener of the shop: + +```nginx +server { + listen 443 ssl; + http2 on; # nginx 1.25.1 and later + # before 1.25.1: listen 443 ssl http2; + server_name domain.tld; + # ... +} +``` + +Browsers only use HTTP/2 over TLS, so the shop needs HTTPS. If a CDN or a proxy terminates TLS, +HTTP/2 has to be enabled there. + +## Precompressed assets + +In production, the Flexy and `default-twig` themes enable `framework.asset_mapper.precompress` +(Symfony 7.3 and later). `php Thelia asset-map:compile` then writes `.br`, `.zst` and `.gz` +copies next to each compiled text asset: CSS, JavaScript, JSON, SVG, TTF, OTF, XML, HTML and +a few other text formats, which is Symfony's default list. WOFF2 fonts and images are already compressed and are +left alone. + +The formats are detected on the machine that runs the compile: + +| Format | Needs | +| --- | --- | +| Brotli (`.br`) | the `brotli` command or the PHP `brotli` extension | +| Zstandard (`.zst`) | the `zstd` command or the PHP `zstd` extension | +| Gzip (`.gz`) | nothing, zlib is always available | + +A missing tool is skipped with a warning and the compile goes on. Install `brotli` (and +optionally `zstd`) on the build machine to get those formats. The same compile rebuilds the +Flexy Tailwind stylesheet minified and the back-office Sass stylesheet compressed. Nothing +changes in development: the AssetMapper dev server serves the sources and never compresses. + +To turn precompression off, or to restrict it, set it in a file of your project, for example +`config/packages/asset_mapper.yaml`. Each list replaces the default one, so give it in full: + +```yaml +framework: + asset_mapper: + precompress: + enabled: false +``` + +```yaml +framework: + asset_mapper: + precompress: + formats: [brotli, gzip] + extensions: [css, js, json, svg] +``` + +### Minifying JavaScript + +The default theme does not minify its JavaScript: the first-party code is small, and the vendor +files downloaded from jsDelivr are already minified, so precompression covers most of the gain. +On the demo shop, minifying the JavaScript a page loads saves about 7 KB on the home page +and about 10 KB on a product page once Brotli has compressed it, about 4 KB of +which come from `live_controller.js`. +A project that ships heavier JavaScript can add `sensiolabs/minify-bundle`, a standalone binary +that needs no Node. It hooks into `asset-map:compile` and minifies the assets while they are +copied: + +```bash +composer require sensiolabs/minify-bundle +``` + +Its options live in `config/packages/sensiolabs_minify.yaml`. By default it minifies CSS and +JavaScript, and ignores vendor files (`ignore_vendor: true`). It downloads the binary from +GitHub in debug mode only (`download_binary: '%kernel.debug%'`), so on a build machine, install +`minify` and set `local_binary: 'auto'` so the bundle finds it: + +```yaml +sensiolabs_minify: + minify: + local_binary: 'auto' +``` + +See the [bundle README](https://github.com/SensioLabs/minify-bundle) for the other options. + ## Required writable directories Ensure Nginx (www-data) has write access to: diff --git a/docs/upgrading/update.md b/docs/upgrading/update.md index 48653481e..58915176a 100644 --- a/docs/upgrading/update.md +++ b/docs/upgrading/update.md @@ -127,6 +127,12 @@ The first two rebuild the front-office assets, the last one the back-office styl any command the console does not carry: each comes from a package the corresponding template requires. +In production, also run `php Thelia asset-map:compile`. It writes the files served from +`public/assets/`, with the minified CSS and the precompressed `.br`, `.zst` and `.gz` copies. +Install `brotli` (and optionally `zstd`) on the machine that runs it to get those formats, and +see [Nginx configuration](../getting-started/nginx-configuration.md#precompressed-assets) for the +server side. + ## 5. Update the modules Modules keep their own version numbers, so they update separately: diff --git a/versioned_docs/version-3.2/getting-started/apache-configuration.md b/versioned_docs/version-3.2/getting-started/apache-configuration.md index 0492d1bbc..d848b07b6 100644 --- a/versioned_docs/version-3.2/getting-started/apache-configuration.md +++ b/versioned_docs/version-3.2/getting-started/apache-configuration.md @@ -43,6 +43,82 @@ the class that writes it. An `.htaccess` already present in the directory is lef these headers to it yourself. When `document_cache_dir_from_web_root` moves the document cache, the file goes in that directory. +## Theme assets + +`php Thelia asset-map:compile` writes the theme files to `public/assets/` with a digest in +their names. In production it also writes a `.br`, a `.zst` and a `.gz` copy next to each text +file (see [Precompressed assets](./nginx-configuration.md#precompressed-assets)). From Thelia 3.3, +the `public/.htaccess` of the shop serves the `.br` and `.gz` copies when the browser accepts +them (it needs `mod_rewrite` and `mod_headers`; without `mod_headers` the original file is sent). +On an older `.htaccess`, add these rules to it. If the virtual host does not read `.htaccess` +files (`AllowOverride None`), put them in the `` block of `public/` instead: while +the `.htaccess` is read, its rewrite rules replace those of the `` block, which are +then ignored. + +```apache + + + RewriteEngine On + + # Brotli, if the browser accepts it and the original file exists + RewriteCond %{HTTP:Accept-Encoding} br + RewriteCond %{REQUEST_FILENAME} -f + RewriteCond %{REQUEST_FILENAME}.br -s + RewriteRule ^(assets/.*)\.(css|js|json|svg)$ $1.$2.br [QSA] + + # Gzip, if the browser accepts it and the original file exists + RewriteCond %{HTTP:Accept-Encoding} gzip + RewriteCond %{REQUEST_FILENAME} -f + RewriteCond %{REQUEST_FILENAME}.gz -s + RewriteRule ^(assets/.*)\.(css|js|json|svg)$ $1.$2.gz [QSA] + + # Right content type, and no second compression by mod_brotli or mod_deflate + RewriteRule \.css\.(br|gz)$ - [T=text/css,E=no-brotli:1,E=no-gzip:1] + RewriteRule \.js\.(br|gz)$ - [T=text/javascript,E=no-brotli:1,E=no-gzip:1] + RewriteRule \.json\.(br|gz)$ - [T=application/json,E=no-brotli:1,E=no-gzip:1] + RewriteRule \.svg\.(br|gz)$ - [T=image/svg+xml,E=no-brotli:1,E=no-gzip:1] + + + Header append Content-Encoding br + Header append Vary Accept-Encoding + + + Header append Content-Encoding gzip + Header append Vary Accept-Encoding + + + +``` + +The rules follow the precompressed content pattern of the Apache manual +([mod_brotli](https://httpd.apache.org/docs/2.4/mod/mod_brotli.html#precompressed) and +[mod_deflate](https://httpd.apache.org/docs/2.4/mod/mod_deflate.html#precompressed)). Two +details differ: + +- The `public/.htaccess` of Thelia already compresses text on the fly with `mod_deflate` and + `mod_brotli`. `E=no-brotli:1,E=no-gzip:1` stops them from compressing a file that is already + compressed. +- `RewriteCond %{REQUEST_FILENAME} -f` requires the original file to exist, so a compressed copy + is never served for a path whose original was removed. + +The rules only apply under `assets/`. Thelia's `.htaccess` sends every other request that is not +a file to `index.php`, and it does not touch a file that exists. + +## HTTP/2 + +The Flexy theme serves one JavaScript file per module through an import map: the home page +references about 80 module URLs and 15 `modulepreload` links. Over HTTP/1.1 these requests queue +on six connections per host. A server without HTTP/2 slows this model down noticeably, so enable +it on the HTTPS vhost: + +```apache +Protocols h2 http/1.1 +``` + +This needs `mod_http2` (`a2enmod http2` on Debian) and an MPM that supports it: `mpm_event` or +`mpm_worker`, not `mpm_prefork`. With `mpm_prefork`, switch to `mpm_event` and run PHP through +PHP-FPM (`proxy_fcgi`). Browsers only use HTTP/2 over TLS, so the shop needs HTTPS. + ## Required writable directories Apache needs write access to these directories: diff --git a/versioned_docs/version-3.2/getting-started/cdn.md b/versioned_docs/version-3.2/getting-started/cdn.md index b887f52e9..dc375d4c1 100644 --- a/versioned_docs/version-3.2/getting-started/cdn.md +++ b/versioned_docs/version-3.2/getting-started/cdn.md @@ -22,7 +22,9 @@ URLs do not change, the back office works as before, and Thelia needs three sett ### 1. Cache headers on the origin -Add to the shop's server block (see the [nginx page](./nginx-configuration.md)): +Add to the shop's server block (see the [nginx page](./nginx-configuration.md), which extends +the `/assets/` location below with `gzip_static` and `brotli_static`: keep a single `/assets/` +location): ```nginx # Theme files: the name carries a digest of the content. @@ -131,7 +133,8 @@ the shop domain. - A deploy needs no purge: a changed theme file gets a new name. Run `php Thelia asset-map:compile` on each deploy (`bin/install` does not), again after `thelia:cache:clear` (it empties `public/assets` unless `--without-assets`), and - `importmap:install` after updating the theme package. + `importmap:install` after updating the theme package. In production, this command also + writes the minified CSS and the precompressed `.br`, `.zst` and `.gz` files. - Never set `liip_imagine.webp.generate: true`: every image then goes through a private `302` that no CDN caches. To check, the second load of a product page must show no `/media/cache/resolve/` in `src` or `srcset` (the zoom link `data-zoom-src` may keep one): diff --git a/versioned_docs/version-3.2/getting-started/nginx-configuration.md b/versioned_docs/version-3.2/getting-started/nginx-configuration.md index 4908102f9..e9fc75a3e 100644 --- a/versioned_docs/version-3.2/getting-started/nginx-configuration.md +++ b/versioned_docs/version-3.2/getting-started/nginx-configuration.md @@ -98,6 +98,130 @@ not read it, so a shop served by nginx needs this block. It is required from The 3.1.2 on: add it to the server block of an existing shop when you update. If `document_cache_dir_from_web_root` moves the document cache, change the path of the location. +## Theme assets + +`php Thelia asset-map:compile` writes the theme files to `public/assets/` with a digest in +their names. In production it also writes a `.br`, a `.zst` and a `.gz` copy next to each text +file (see [Precompressed assets](#precompressed-assets) below). Serve them with a dedicated +location: + +```nginx +# Theme files: the name carries a digest of the content. +location ^~ /assets/ { + gzip_static on; + brotli_static on; # needs the ngx_brotli module, remove the line without it + gzip_vary on; + + add_header Cache-Control "public, max-age=31536000, immutable" always; + add_header Access-Control-Allow-Origin "*" always; + + # The original file must exist: gzip_static and brotli_static do not check it + try_files $uri =404; +} +``` + +The `^~` prefix makes this location win over the static files location above, so the cache +headers are repeated inside it. This is the same `/assets/` location as in +[Serving files from a CDN](./cdn.md): keep a single one in the server block. + +`gzip_static` is part of the standard nginx build on Debian. `brotli_static` comes from the +ngx_brotli module, packaged as `libnginx-mod-http-brotli-static` on Debian. nginx +does not serve `.zst` files natively: those are for servers and proxies that support them. + +`gzip_static on` and `brotli_static on` answer with `$uri.gz` or `$uri.br` when the browser +accepts the encoding, and they do so without checking that `$uri` exists. Without +`try_files $uri =404`, a request for a path whose original file was removed would still receive +its stale compressed copy. With it, a missing original is a 404 and no compressed file is ever +served alone. `gzip_vary on` adds `Vary: Accept-Encoding`, so a cache keeps the compressed and +the plain versions apart. + +## HTTP/2 + +The Flexy theme serves one JavaScript file per module through an import map: the home page +references about 80 module URLs and 15 `modulepreload` links. Over HTTP/1.1 these requests queue +on six connections per host. A server without HTTP/2 slows this model down noticeably, so enable +it on the TLS listener of the shop: + +```nginx +server { + listen 443 ssl; + http2 on; # nginx 1.25.1 and later + # before 1.25.1: listen 443 ssl http2; + server_name domain.tld; + # ... +} +``` + +Browsers only use HTTP/2 over TLS, so the shop needs HTTPS. If a CDN or a proxy terminates TLS, +HTTP/2 has to be enabled there. + +## Precompressed assets + +In production, the Flexy and `default-twig` themes enable `framework.asset_mapper.precompress` +(Symfony 7.3 and later). `php Thelia asset-map:compile` then writes `.br`, `.zst` and `.gz` +copies next to each compiled text asset: CSS, JavaScript, JSON, SVG, TTF, OTF, XML, HTML and +a few other text formats, which is Symfony's default list. WOFF2 fonts and images are already compressed and are +left alone. + +The formats are detected on the machine that runs the compile: + +| Format | Needs | +| --- | --- | +| Brotli (`.br`) | the `brotli` command or the PHP `brotli` extension | +| Zstandard (`.zst`) | the `zstd` command or the PHP `zstd` extension | +| Gzip (`.gz`) | nothing, zlib is always available | + +A missing tool is skipped with a warning and the compile goes on. Install `brotli` (and +optionally `zstd`) on the build machine to get those formats. The same compile rebuilds the +Flexy Tailwind stylesheet minified and the back-office Sass stylesheet compressed. Nothing +changes in development: the AssetMapper dev server serves the sources and never compresses. + +To turn precompression off, or to restrict it, set it in a file of your project, for example +`config/packages/asset_mapper.yaml`. Each list replaces the default one, so give it in full: + +```yaml +framework: + asset_mapper: + precompress: + enabled: false +``` + +```yaml +framework: + asset_mapper: + precompress: + formats: [brotli, gzip] + extensions: [css, js, json, svg] +``` + +### Minifying JavaScript + +The default theme does not minify its JavaScript: the first-party code is small, and the vendor +files downloaded from jsDelivr are already minified, so precompression covers most of the gain. +On the demo shop, minifying the JavaScript a page loads saves about 7 KB on the home page +and about 10 KB on a product page once Brotli has compressed it, about 4 KB of +which come from `live_controller.js`. +A project that ships heavier JavaScript can add `sensiolabs/minify-bundle`, a standalone binary +that needs no Node. It hooks into `asset-map:compile` and minifies the assets while they are +copied: + +```bash +composer require sensiolabs/minify-bundle +``` + +Its options live in `config/packages/sensiolabs_minify.yaml`. By default it minifies CSS and +JavaScript, and ignores vendor files (`ignore_vendor: true`). It downloads the binary from +GitHub in debug mode only (`download_binary: '%kernel.debug%'`), so on a build machine, install +`minify` and set `local_binary: 'auto'` so the bundle finds it: + +```yaml +sensiolabs_minify: + minify: + local_binary: 'auto' +``` + +See the [bundle README](https://github.com/SensioLabs/minify-bundle) for the other options. + ## Required writable directories Ensure Nginx (www-data) has write access to: diff --git a/versioned_docs/version-3.2/upgrading/update.md b/versioned_docs/version-3.2/upgrading/update.md index 48653481e..58915176a 100644 --- a/versioned_docs/version-3.2/upgrading/update.md +++ b/versioned_docs/version-3.2/upgrading/update.md @@ -127,6 +127,12 @@ The first two rebuild the front-office assets, the last one the back-office styl any command the console does not carry: each comes from a package the corresponding template requires. +In production, also run `php Thelia asset-map:compile`. It writes the files served from +`public/assets/`, with the minified CSS and the precompressed `.br`, `.zst` and `.gz` copies. +Install `brotli` (and optionally `zstd`) on the machine that runs it to get those formats, and +see [Nginx configuration](../getting-started/nginx-configuration.md#precompressed-assets) for the +server side. + ## 5. Update the modules Modules keep their own version numbers, so they update separately: From c0abd0ff2aebdad5802557c008acd2690534e61b Mon Sep 17 00:00:00 2001 From: Pierre Baguet Date: Fri, 23 Oct 2026 16:59:05 +0200 Subject: [PATCH 2/2] docs(#296): keep the unreleased work out of the published 3.2 version --- .../getting-started/apache-configuration.md | 76 ----------- .../version-3.2/getting-started/cdn.md | 7 +- .../getting-started/nginx-configuration.md | 124 ------------------ .../version-3.2/upgrading/update.md | 6 - 4 files changed, 2 insertions(+), 211 deletions(-) diff --git a/versioned_docs/version-3.2/getting-started/apache-configuration.md b/versioned_docs/version-3.2/getting-started/apache-configuration.md index d848b07b6..0492d1bbc 100644 --- a/versioned_docs/version-3.2/getting-started/apache-configuration.md +++ b/versioned_docs/version-3.2/getting-started/apache-configuration.md @@ -43,82 +43,6 @@ the class that writes it. An `.htaccess` already present in the directory is lef these headers to it yourself. When `document_cache_dir_from_web_root` moves the document cache, the file goes in that directory. -## Theme assets - -`php Thelia asset-map:compile` writes the theme files to `public/assets/` with a digest in -their names. In production it also writes a `.br`, a `.zst` and a `.gz` copy next to each text -file (see [Precompressed assets](./nginx-configuration.md#precompressed-assets)). From Thelia 3.3, -the `public/.htaccess` of the shop serves the `.br` and `.gz` copies when the browser accepts -them (it needs `mod_rewrite` and `mod_headers`; without `mod_headers` the original file is sent). -On an older `.htaccess`, add these rules to it. If the virtual host does not read `.htaccess` -files (`AllowOverride None`), put them in the `` block of `public/` instead: while -the `.htaccess` is read, its rewrite rules replace those of the `` block, which are -then ignored. - -```apache - - - RewriteEngine On - - # Brotli, if the browser accepts it and the original file exists - RewriteCond %{HTTP:Accept-Encoding} br - RewriteCond %{REQUEST_FILENAME} -f - RewriteCond %{REQUEST_FILENAME}.br -s - RewriteRule ^(assets/.*)\.(css|js|json|svg)$ $1.$2.br [QSA] - - # Gzip, if the browser accepts it and the original file exists - RewriteCond %{HTTP:Accept-Encoding} gzip - RewriteCond %{REQUEST_FILENAME} -f - RewriteCond %{REQUEST_FILENAME}.gz -s - RewriteRule ^(assets/.*)\.(css|js|json|svg)$ $1.$2.gz [QSA] - - # Right content type, and no second compression by mod_brotli or mod_deflate - RewriteRule \.css\.(br|gz)$ - [T=text/css,E=no-brotli:1,E=no-gzip:1] - RewriteRule \.js\.(br|gz)$ - [T=text/javascript,E=no-brotli:1,E=no-gzip:1] - RewriteRule \.json\.(br|gz)$ - [T=application/json,E=no-brotli:1,E=no-gzip:1] - RewriteRule \.svg\.(br|gz)$ - [T=image/svg+xml,E=no-brotli:1,E=no-gzip:1] - - - Header append Content-Encoding br - Header append Vary Accept-Encoding - - - Header append Content-Encoding gzip - Header append Vary Accept-Encoding - - - -``` - -The rules follow the precompressed content pattern of the Apache manual -([mod_brotli](https://httpd.apache.org/docs/2.4/mod/mod_brotli.html#precompressed) and -[mod_deflate](https://httpd.apache.org/docs/2.4/mod/mod_deflate.html#precompressed)). Two -details differ: - -- The `public/.htaccess` of Thelia already compresses text on the fly with `mod_deflate` and - `mod_brotli`. `E=no-brotli:1,E=no-gzip:1` stops them from compressing a file that is already - compressed. -- `RewriteCond %{REQUEST_FILENAME} -f` requires the original file to exist, so a compressed copy - is never served for a path whose original was removed. - -The rules only apply under `assets/`. Thelia's `.htaccess` sends every other request that is not -a file to `index.php`, and it does not touch a file that exists. - -## HTTP/2 - -The Flexy theme serves one JavaScript file per module through an import map: the home page -references about 80 module URLs and 15 `modulepreload` links. Over HTTP/1.1 these requests queue -on six connections per host. A server without HTTP/2 slows this model down noticeably, so enable -it on the HTTPS vhost: - -```apache -Protocols h2 http/1.1 -``` - -This needs `mod_http2` (`a2enmod http2` on Debian) and an MPM that supports it: `mpm_event` or -`mpm_worker`, not `mpm_prefork`. With `mpm_prefork`, switch to `mpm_event` and run PHP through -PHP-FPM (`proxy_fcgi`). Browsers only use HTTP/2 over TLS, so the shop needs HTTPS. - ## Required writable directories Apache needs write access to these directories: diff --git a/versioned_docs/version-3.2/getting-started/cdn.md b/versioned_docs/version-3.2/getting-started/cdn.md index dc375d4c1..b887f52e9 100644 --- a/versioned_docs/version-3.2/getting-started/cdn.md +++ b/versioned_docs/version-3.2/getting-started/cdn.md @@ -22,9 +22,7 @@ URLs do not change, the back office works as before, and Thelia needs three sett ### 1. Cache headers on the origin -Add to the shop's server block (see the [nginx page](./nginx-configuration.md), which extends -the `/assets/` location below with `gzip_static` and `brotli_static`: keep a single `/assets/` -location): +Add to the shop's server block (see the [nginx page](./nginx-configuration.md)): ```nginx # Theme files: the name carries a digest of the content. @@ -133,8 +131,7 @@ the shop domain. - A deploy needs no purge: a changed theme file gets a new name. Run `php Thelia asset-map:compile` on each deploy (`bin/install` does not), again after `thelia:cache:clear` (it empties `public/assets` unless `--without-assets`), and - `importmap:install` after updating the theme package. In production, this command also - writes the minified CSS and the precompressed `.br`, `.zst` and `.gz` files. + `importmap:install` after updating the theme package. - Never set `liip_imagine.webp.generate: true`: every image then goes through a private `302` that no CDN caches. To check, the second load of a product page must show no `/media/cache/resolve/` in `src` or `srcset` (the zoom link `data-zoom-src` may keep one): diff --git a/versioned_docs/version-3.2/getting-started/nginx-configuration.md b/versioned_docs/version-3.2/getting-started/nginx-configuration.md index e9fc75a3e..4908102f9 100644 --- a/versioned_docs/version-3.2/getting-started/nginx-configuration.md +++ b/versioned_docs/version-3.2/getting-started/nginx-configuration.md @@ -98,130 +98,6 @@ not read it, so a shop served by nginx needs this block. It is required from The 3.1.2 on: add it to the server block of an existing shop when you update. If `document_cache_dir_from_web_root` moves the document cache, change the path of the location. -## Theme assets - -`php Thelia asset-map:compile` writes the theme files to `public/assets/` with a digest in -their names. In production it also writes a `.br`, a `.zst` and a `.gz` copy next to each text -file (see [Precompressed assets](#precompressed-assets) below). Serve them with a dedicated -location: - -```nginx -# Theme files: the name carries a digest of the content. -location ^~ /assets/ { - gzip_static on; - brotli_static on; # needs the ngx_brotli module, remove the line without it - gzip_vary on; - - add_header Cache-Control "public, max-age=31536000, immutable" always; - add_header Access-Control-Allow-Origin "*" always; - - # The original file must exist: gzip_static and brotli_static do not check it - try_files $uri =404; -} -``` - -The `^~` prefix makes this location win over the static files location above, so the cache -headers are repeated inside it. This is the same `/assets/` location as in -[Serving files from a CDN](./cdn.md): keep a single one in the server block. - -`gzip_static` is part of the standard nginx build on Debian. `brotli_static` comes from the -ngx_brotli module, packaged as `libnginx-mod-http-brotli-static` on Debian. nginx -does not serve `.zst` files natively: those are for servers and proxies that support them. - -`gzip_static on` and `brotli_static on` answer with `$uri.gz` or `$uri.br` when the browser -accepts the encoding, and they do so without checking that `$uri` exists. Without -`try_files $uri =404`, a request for a path whose original file was removed would still receive -its stale compressed copy. With it, a missing original is a 404 and no compressed file is ever -served alone. `gzip_vary on` adds `Vary: Accept-Encoding`, so a cache keeps the compressed and -the plain versions apart. - -## HTTP/2 - -The Flexy theme serves one JavaScript file per module through an import map: the home page -references about 80 module URLs and 15 `modulepreload` links. Over HTTP/1.1 these requests queue -on six connections per host. A server without HTTP/2 slows this model down noticeably, so enable -it on the TLS listener of the shop: - -```nginx -server { - listen 443 ssl; - http2 on; # nginx 1.25.1 and later - # before 1.25.1: listen 443 ssl http2; - server_name domain.tld; - # ... -} -``` - -Browsers only use HTTP/2 over TLS, so the shop needs HTTPS. If a CDN or a proxy terminates TLS, -HTTP/2 has to be enabled there. - -## Precompressed assets - -In production, the Flexy and `default-twig` themes enable `framework.asset_mapper.precompress` -(Symfony 7.3 and later). `php Thelia asset-map:compile` then writes `.br`, `.zst` and `.gz` -copies next to each compiled text asset: CSS, JavaScript, JSON, SVG, TTF, OTF, XML, HTML and -a few other text formats, which is Symfony's default list. WOFF2 fonts and images are already compressed and are -left alone. - -The formats are detected on the machine that runs the compile: - -| Format | Needs | -| --- | --- | -| Brotli (`.br`) | the `brotli` command or the PHP `brotli` extension | -| Zstandard (`.zst`) | the `zstd` command or the PHP `zstd` extension | -| Gzip (`.gz`) | nothing, zlib is always available | - -A missing tool is skipped with a warning and the compile goes on. Install `brotli` (and -optionally `zstd`) on the build machine to get those formats. The same compile rebuilds the -Flexy Tailwind stylesheet minified and the back-office Sass stylesheet compressed. Nothing -changes in development: the AssetMapper dev server serves the sources and never compresses. - -To turn precompression off, or to restrict it, set it in a file of your project, for example -`config/packages/asset_mapper.yaml`. Each list replaces the default one, so give it in full: - -```yaml -framework: - asset_mapper: - precompress: - enabled: false -``` - -```yaml -framework: - asset_mapper: - precompress: - formats: [brotli, gzip] - extensions: [css, js, json, svg] -``` - -### Minifying JavaScript - -The default theme does not minify its JavaScript: the first-party code is small, and the vendor -files downloaded from jsDelivr are already minified, so precompression covers most of the gain. -On the demo shop, minifying the JavaScript a page loads saves about 7 KB on the home page -and about 10 KB on a product page once Brotli has compressed it, about 4 KB of -which come from `live_controller.js`. -A project that ships heavier JavaScript can add `sensiolabs/minify-bundle`, a standalone binary -that needs no Node. It hooks into `asset-map:compile` and minifies the assets while they are -copied: - -```bash -composer require sensiolabs/minify-bundle -``` - -Its options live in `config/packages/sensiolabs_minify.yaml`. By default it minifies CSS and -JavaScript, and ignores vendor files (`ignore_vendor: true`). It downloads the binary from -GitHub in debug mode only (`download_binary: '%kernel.debug%'`), so on a build machine, install -`minify` and set `local_binary: 'auto'` so the bundle finds it: - -```yaml -sensiolabs_minify: - minify: - local_binary: 'auto' -``` - -See the [bundle README](https://github.com/SensioLabs/minify-bundle) for the other options. - ## Required writable directories Ensure Nginx (www-data) has write access to: diff --git a/versioned_docs/version-3.2/upgrading/update.md b/versioned_docs/version-3.2/upgrading/update.md index 58915176a..48653481e 100644 --- a/versioned_docs/version-3.2/upgrading/update.md +++ b/versioned_docs/version-3.2/upgrading/update.md @@ -127,12 +127,6 @@ The first two rebuild the front-office assets, the last one the back-office styl any command the console does not carry: each comes from a package the corresponding template requires. -In production, also run `php Thelia asset-map:compile`. It writes the files served from -`public/assets/`, with the minified CSS and the precompressed `.br`, `.zst` and `.gz` copies. -Install `brotli` (and optionally `zstd`) on the machine that runs it to get those formats, and -see [Nginx configuration](../getting-started/nginx-configuration.md#precompressed-assets) for the -server side. - ## 5. Update the modules Modules keep their own version numbers, so they update separately: