Extensions: caching
Cache the extension bundle and its lazy chunks for a year, with headers that still let a deploy reach every browser (v2.20.0+).
A returning operator then loads them from the browser cache instead of the network. The bundle (npm run build:extensions) is a static file under public/vendor/martis-user/, and its URL changes whenever its content does.
What Martis does
- The entry URL carries a version. The shell emits
/vendor/martis-user/extensions.js?v=<12 hex>, a hash of the file's content, for every root-relativeMARTIS_EXTENSIONSURL that is a file underpublic/. The URL changes when, and only when, the file does. Absolute URLs, URLs that already have a query string, and files that do not exist are emitted as configured. Turn it off withMARTIS_EXTENSIONS_VERSIONED_URLS=false(martis.extensions_versioned_urls). - Chunks have hashed names. A tool or card registered with
componentRegistry.registerLazy(see Overrides) is built intochunks/<name>-<hash>.jsnext to the entry. The hash is in the file name, so a chunk's URL never serves different content. - Stale tabs recover. A tab opened before a deploy still points at the old chunk names. When one is gone, Martis reloads the tab once and shows an error state if that does not help (
MARTIS_EXTENSIONS_RELOAD_ON_STALE=falsedisables the reload). Keep the previous release's chunks on disk for a short while after a deploy if you want such tabs to keep working without a reload.
What to send
| Request | Header |
|---|---|
The entry with ?v= (/vendor/martis-user/extensions.js?v=...) | Cache-Control: public, max-age=31536000, immutable |
Hashed chunks (/vendor/martis-user/chunks/*) | Cache-Control: public, max-age=31536000, immutable |
The bare entry (no ?v=) and anything else under /vendor/martis-user/ | Cache-Control: no-cache |
immutable is safe for the first two rows because their URLs change with their content. no-cache does not forbid caching: the browser stores the file and revalidates it (ETag / Last-Modified) on each use, so an unversioned URL never serves an old bundle. The ?v= rule only applies while martis.extensions_versioned_urls is on: with it off, send no-cache for the entry too.
The snippets below assume the files are served from public/ at /vendor/martis-user/. Adjust the path if you changed build.outDir.
Caddy
example.com { root * /var/www/app/public @entryVersioned { path /vendor/martis-user/extensions.js query v=* } @chunks path /vendor/martis-user/chunks/* @folder path /vendor/martis-user/* # The two specific rules set the header; the catch-all (`?`) only sets # it where no earlier rule did, so it is the default for the folder. header @entryVersioned Cache-Control "public, max-age=31536000, immutable" header @chunks Cache-Control "public, max-age=31536000, immutable" header @folder ?Cache-Control "no-cache" php_fastcgi unix//run/php/php-fpm.sock file_server}The ? prefix of ?Cache-Control sets the header only when none was set before it, which makes the third rule a default. Keep it after the other two.
nginx
server { root /var/www/app/public; # Hashed chunks: the hash is in the file name. location ^~ /vendor/martis-user/chunks/ { add_header Cache-Control "public, max-age=31536000, immutable" always; try_files $uri =404; } # The entry: immutable with ?v=, revalidated without it. location = /vendor/martis-user/extensions.js { set $martis_cache "no-cache"; if ($arg_v != "") { set $martis_cache "public, max-age=31536000, immutable"; } add_header Cache-Control $martis_cache always; try_files $uri =404; } # Everything else in the folder. location ^~ /vendor/martis-user/ { add_header Cache-Control "no-cache" always; try_files $uri =404; }}add_header in a location replaces the headers of the enclosing server, so repeat any you need (security headers, for instance) in each block.
Apache
<IfModule mod_headers.c> # Default for the folder: revalidate. <LocationMatch "^/vendor/martis-user/"> Header set Cache-Control "no-cache" </LocationMatch> # Hashed chunks. <LocationMatch "^/vendor/martis-user/chunks/"> Header set Cache-Control "public, max-age=31536000, immutable" </LocationMatch> # The entry, only when it is requested with ?v=. SetEnvIf Request_URI "^/vendor/martis-user/extensions\.js$" MARTIS_ENTRY SetEnvIf Query_String "(^|&)v=" MARTIS_VERSIONED <If "%{ENV:MARTIS_ENTRY} == '1' && %{ENV:MARTIS_VERSIONED} == '1'"> Header set Cache-Control "public, max-age=31536000, immutable" </If></IfModule>If the app sits behind a CDN, make sure it honours the origin's Cache-Control and keeps the query string in its cache key; a CDN that drops ?v= serves one entry to every version.
Checking it
curl -sI 'https://example.com/vendor/martis-user/extensions.js?v=abc' | grep -i cache-controlcurl -sI 'https://example.com/vendor/martis-user/extensions.js' | grep -i cache-controlcurl -sI 'https://example.com/vendor/martis-user/chunks/Reports-3f9c1a2b.js' | grep -i cache-controlThe first and third answer public, max-age=31536000, immutable; the second answers no-cache. In the browser, the Network panel shows (disk cache) for the first two on a second visit.