dea08f9c47
Adds the manifest, icons and service worker that make the app installable, and offers it as a toast once Chrome says it qualifies. Declining snoozes the offer for a month; installing ends it. A waiting service worker never activates on its own. Reloading the page under a listener to swap in a new build would cut the song they are in the middle of, so updates land on the next cold start instead. Audio is kept out of the cache entirely: range requests and multi-megabyte bodies do not belong in a shell cache. Artwork is cached, and the SPA navigation fallback denies /api so it cannot swallow the event stream. Installed on Android the app paints edge to edge, so the transport pads itself past the gesture bar. MediaSession gains setPositionState, which is what gives the notification shade a seek bar that moves. Three things kept the bundle from ever being compressed, each hiding the next: the nginx image ships with gzip off, gzip_proxied defaults to off and skips anything carrying a Via header, and gzip_http_version defaults to 1.1 while the host proxy speaks 1.0. With those fixed and the pages split per route, the first load goes from 555KB to 60KB of app code plus a vendor chunk that survives redeploys. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
95 lines
3.4 KiB
Plaintext
95 lines
3.4 KiB
Plaintext
server {
|
|
listen 80;
|
|
|
|
# The nginx image ships with gzip off, so the bundle went out raw: 555KB of
|
|
# JS where gzip sends 167KB. Only text formats are listed — images, fonts
|
|
# (woff2) and audio are already compressed and would only burn CPU.
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_min_length 1024;
|
|
# The public URL sits behind another proxy, which adds a Via header. With
|
|
# the default gzip_proxied off, nginx refuses to compress those responses.
|
|
gzip_proxied any;
|
|
# That same proxy speaks HTTP/1.0 upstream (nginx proxy_pass defaults to it),
|
|
# and gzip_http_version defaults to 1.1 — so without this nothing here is
|
|
# ever compressed, however the client asks.
|
|
gzip_http_version 1.0;
|
|
gzip_comp_level 6;
|
|
gzip_types
|
|
application/javascript
|
|
application/json
|
|
application/manifest+json
|
|
image/svg+xml
|
|
text/css
|
|
text/plain;
|
|
|
|
location / {
|
|
root /usr/share/nginx/html;
|
|
index index.html index.htm;
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
|
|
# A cached service worker or shell would pin the app to an old build, since
|
|
# both are the files that point at every hashed asset. Assets themselves are
|
|
# content-hashed by Vite, so they can be held forever.
|
|
# nginx's stock mime.types has no .webmanifest entry, so it would otherwise
|
|
# go out as application/octet-stream.
|
|
location = /manifest.webmanifest {
|
|
root /usr/share/nginx/html;
|
|
default_type application/manifest+json;
|
|
add_header Cache-Control "no-cache, must-revalidate";
|
|
}
|
|
|
|
location = /sw.js {
|
|
root /usr/share/nginx/html;
|
|
add_header Cache-Control "no-cache, must-revalidate";
|
|
}
|
|
|
|
location = /index.html {
|
|
root /usr/share/nginx/html;
|
|
add_header Cache-Control "no-cache, must-revalidate";
|
|
}
|
|
|
|
location /assets/ {
|
|
root /usr/share/nginx/html;
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
}
|
|
|
|
# Admin endpoints need the admin key, not the regular API key. This prefix
|
|
# location is longer than "/api", and nginx picks the longest matching
|
|
# prefix location, so it wins for /api/admin/* while /api handles the rest.
|
|
location /api/admin/ {
|
|
proxy_pass http://backend:3000;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection 'upgrade';
|
|
proxy_set_header Host $host;
|
|
proxy_set_header Authorization "Bearer ${MUZICK_ADMIN_KEY}";
|
|
proxy_cache_bypass $http_upgrade;
|
|
}
|
|
|
|
# The cross-device event stream is long-lived and must arrive unbuffered:
|
|
# with proxy buffering on, nginx holds each event until it has a chunk worth
|
|
# forwarding, which is exactly the latency this channel exists to avoid.
|
|
location /api/playback/stream {
|
|
proxy_pass http://backend:3000;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header Authorization "Bearer ${MUZICK_API_KEY}";
|
|
proxy_set_header Connection '';
|
|
proxy_buffering off;
|
|
proxy_cache off;
|
|
proxy_read_timeout 1h;
|
|
}
|
|
|
|
location /api {
|
|
proxy_pass http://backend:3000;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection 'upgrade';
|
|
proxy_set_header Host $host;
|
|
proxy_set_header Authorization "Bearer ${MUZICK_API_KEY}";
|
|
proxy_cache_bypass $http_upgrade;
|
|
}
|
|
}
|