`try_files` served the SPA fallback inside `location /`, so `/` and every client route went out without `Cache-Control: no-cache`. The fallback is now the last argument, an internal redirect to the exact no-cache location. `src/csp.check.ts` recomputes the inline theme script's sha256 and fails `bun run check` with the new value when the CSP no longer names it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
73 lines
2.5 KiB
Nginx Configuration File
73 lines
2.5 KiB
Nginx Configuration File
# The live socket carries its credential as `?token=`, and so does a password
|
|
# reset link, which then rides along as the Referer of everything that page
|
|
# loads. nginx's own format logs both as sent; this one masks the value.
|
|
map $request $request_masked {
|
|
"~^(?<request_head>.*?[?&][\w-]*token=)[^&\s]*(?<request_tail>.*)$" "${request_head}***${request_tail}";
|
|
default $request;
|
|
}
|
|
map $http_referer $referer_masked {
|
|
"~^(?<referer_head>.*?[?&][\w-]*token=)[^&\s]*(?<referer_tail>.*)$" "${referer_head}***${referer_tail}";
|
|
default $http_referer;
|
|
}
|
|
log_format masked '$remote_addr - $remote_user [$time_local] "$request_masked" '
|
|
'$status $body_bytes_sent "$referer_masked" '
|
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
|
|
|
server {
|
|
listen 80;
|
|
access_log /var/log/nginx/access.log masked;
|
|
|
|
# nginx's mime.types predates the manifest, so without this it goes out as
|
|
# application/octet-stream.
|
|
types {
|
|
application/manifest+json webmanifest;
|
|
}
|
|
include /etc/nginx/mime.types;
|
|
|
|
# The bundle is ~770 kB of JavaScript and ~85 kB of CSS, and nginx's base
|
|
# image ships gzip commented out — so every first load shipped all of it
|
|
# uncompressed. Roughly a third of the bytes with this on.
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_min_length 1024;
|
|
gzip_proxied any;
|
|
gzip_types
|
|
application/javascript
|
|
application/json
|
|
application/manifest+json
|
|
application/wasm
|
|
image/svg+xml
|
|
text/css
|
|
text/plain;
|
|
|
|
include /etc/nginx/security-headers.conf;
|
|
|
|
# The fallback is the last argument, so it is an internal redirect to the
|
|
# exact location below and `/` and every SPA route get its no-cache too; as
|
|
# a middle argument it was served from here, without one.
|
|
location / {
|
|
root /usr/share/nginx/html;
|
|
index index.html index.htm;
|
|
try_files $uri /index.html;
|
|
}
|
|
|
|
# Vite puts a content hash in every asset's name, so one can never change
|
|
# under its URL. Without this each of the ~50 of them cost a 304 round trip
|
|
# on every reload.
|
|
location /assets/ {
|
|
root /usr/share/nginx/html;
|
|
include /etc/nginx/security-headers.conf;
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
}
|
|
|
|
# The one file that must not be cached: it is what names the current
|
|
# assets, so a stale copy pins the browser to the previous build.
|
|
location = /index.html {
|
|
root /usr/share/nginx/html;
|
|
include /etc/nginx/security-headers.conf;
|
|
add_header Cache-Control "no-cache";
|
|
}
|
|
|
|
include /etc/nginx/extra-conf.d/*.conf;
|
|
}
|