Repository navigation
fix: set Cache-Control headers and enable gzip in the nginx config - #157
Conversation
The nginx config sent no Cache-Control on the app's own files, so browsers relied on heuristic freshness from Last-Modified. Every file in the image has the build time as mtime, so a browser could hold index.html for a day or more, then request hashed bundles a new deploy had removed and receive index.html with a 200 for them. gzip was also off in the base image. - Hashed build output (webpack browser builder: root [name].<16+ hex>.ext): public, max-age=1y, immutable; a missing file is a real 404. - /assets/config/envvars.json (written by entrypoint.sh): no-store. - Everything else, including index.html and src/assets copies: no-cache. - gzip on for HTML, CSS, JS, JSON, SVG and EOT. Fleet rollout of moleculemaker/CLEANDB-frontend#60; this repo's proxy block is carried over unchanged.
entrypoint.sh ran nginx as a child of bash, and bash as PID 1 does not forward SIGTERM. A stopping container therefore ran out its whole termination grace period (measured: `docker stop -t 30` took 30s; with exec it takes 0s). In Kubernetes that meant the old pod kept serving for the full grace period after the new pod was Ready, which is the window where a freshly served index.html asks an old pod for chunks it no longer has. With exec, nginx is PID 1, receives SIGTERM directly and exits at once, so the overlap shrinks to endpoint propagation and every rollout finishes about 30 seconds sooner.
|
Added one more commit: |
|
Checked (independently of the PR body): the head Findings: none. |
Problem
nginx/default.confsets noCache-Controlon the app's own files, so browsers fall back to heuristic freshness based onLast-Modified. Every file in the image carries the build time as its mtime, so on a deployment that has been running for a while a browser can treatindex.htmlas fresh for a day or more without revalidating. After a new image rolls out, that staleindex.htmlrequests hashed bundles that no longer exist, andtry_files $uri /index.htmlanswers withindex.htmland a 200, producing module MIME-type / "failed to fetch dynamically imported module" errors until a hard refresh. Thenginx:1.23.3base image also ships with gzip commented out, so the main bundle goes over the wire uncompressed.This is the fleet rollout of moleculemaker/CLEANDB-frontend#60 (reviewed there). The generator source for new projects is fixed in moleculemaker/frontend-dev-tools#11 and moleculemaker/frontend-template#7.
Change
One file,
nginx/default.conf.[name].<16+ lowercase hex>.<ext>, which is how the legacy webpackbrowserbuilder names every hashed file: bundles, lazy chunks, and the fonts and images referenced from CSS all land at the rootpublic, max-age=31536000, immutableindex.html/assets/config/envvars.jsonno-storeentrypoint.shat container startindex.html, SPA routes,favicon.ico, everything underassets/)no-cachePlus
gzip onwithgzip_vary,gzip_min_length 1024, and types for CSS, JS, JSON, SVG and EOT (HTML is always included; woff/woff2 and images are already compressed).Immutable rule:
^/[^/]+\.[0-9a-f]{16,}\.[A-Za-z0-9]+$. It is case-sensitive and anchored to the root so that files copied verbatim fromsrc/assets(never hashed) are excluded; none of this repo's assets match it. The regex is quoted because nginx otherwise reads the{n}brace as a block opener.The hash is 16 hex characters, not the 20 the
[contenthash:20]template suggests, because build-angular sets webpack'shashFunctiontoxxhash64(a 64-bit digest). Verified against a real Angular 15 build (main.9637a10c19cda006.js); the rule accepts 16 or more so a longer digest in a later version still matches. This is a different rule from the one in CLEANDB-frontend#60, which uses the application builder's-XXXXXXXXformat; that rule would match nothing here.This repo has no proxy location, so none is carried over.
try_files $uri /index.html;becomestry_files $uri /index.html =404;to match the other frontends. No behavior change:/index.htmlalways exists so the=404arm is never reached.Verification
The new file was built into a
FROM nginx:1.23.3image with this repo's Dockerfile COPY lines andentrypoint.sh, started with the env vars fromenvvars.tpl.json, and probed with curl against a productionng buildof clean-frontend (Angular 15, webpackbrowserbuilder,outputHashing: all).nginx -tpassed. Fake files were added for the edge probes (ketcher…at the root to prove the immutable rule wins over any proxy rule; a hashed file with an unlisted extension).//index.html/some/spa/route/main.9637a10c19cda006.js/styles.8f037a6bbdf88809.css/color.dae87a04d07ca92b.png/ketcher.0123456789abcdef0123.js/main.deadbeefdeadbeefdead.js/nope.0123456789abcdef0123.png/assets/config/envvars.json/assets/price_maxsep.csv/favicon.icoSPA route body is index.html: yes
gzip main: 781829 -> 249467 bytes (1 Content-Encoding header)
envvars.json substituted by entrypoint: yes
Not verified: requests to the proxy upstream (the block is unchanged), and behavior behind the ingress. Also not closed: the rolling-update window where a new
index.htmlis served by a new pod and a chunk is requested from an old one; that is now a hard 404 (recoverable with a reload) rather than a heuristically cacheable 200 with HTML, and the fix belongs in the chart.