Files
F-r-ldraledigplannerare/frontend/nginx.conf.template
T
jacobh2 d14f86478a Lay the Almanacka design foundation: tokens, type, shell
New token system in global.css (near-white surfaces, one ink-blue
accent, no sub-12px type), self-hosted Schibsted Grotesk, rewritten
primitives (buttons, fields, modal shell, segmented, disclosure,
skeleton), a borderless 56px header with an SV | EN language toggle,
an Icon component replacing glyphs, page skeletons that keep the
layout during loading, and leave-type colours moved out of TS into
CSS variables. Feature blocks keep their structure for later phases.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 18:25:04 +02:00

86 lines
3.3 KiB
Plaintext

# Rendered by the nginx-unprivileged entrypoint: envsubst expands ${API_UPSTREAM}
# (and only names that exist in the environment — nginx's own $uri/$host/etc.
# are left alone) into /etc/nginx/conf.d/default.conf at container start.
server {
# 8080, not 80: unprivileged nginx runs as uid 101 and cannot bind <1024.
listen 8080;
listen [::]:8080;
server_name _;
root /usr/share/nginx/html;
index index.html;
# The bundle is already gzipped over the wire by this; Brotli would need a
# module the stock image does not carry, so gzip it is.
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_types text/plain text/css application/javascript application/json
image/svg+xml application/manifest+json;
server_tokens off;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header Referrer-Policy no-referrer always;
# Container-level liveness. Served from memory, so a broken bundle or a
# read-only mount cannot make this lie.
location = /healthz {
access_log off;
# default_type, not `add_header Content-Type`: add_header appends, so
# the response would carry two Content-Type headers.
default_type text/plain;
return 200 "ok\n";
}
# Same-origin API. Because the browser only ever talks to this origin, the
# backend's CORS allow-list is not exercised at all in this deployment.
location /api/ {
proxy_pass ${API_UPSTREAM};
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
# The FK-scraper poll endpoints are long-ish; don't cut them at 60s.
proxy_read_timeout 120s;
proxy_buffering off;
}
# Vite emits content-hashed filenames under /assets, so they are immutable.
location /assets/ {
access_log off;
# `expires 1y` would emit its own Cache-Control alongside this one;
# one explicit header is clearer than two contradicting-looking ones.
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}
# Self-hosted webfonts are versioned in their filename (…-v7.woff2), so
# a glyph update ships as a new file and the old one can be cached forever.
location /fonts/ {
access_log off;
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}
# index.html is the only mutable entry point — never cache it, or a deploy
# leaves clients pinned to a stale asset graph.
location = /index.html {
add_header Cache-Control "no-cache" always;
}
# SPA fallback. react-router-dom uses BrowserRouter, so a hard refresh or a
# shared link on a client-only route (/calendar, /settings, ...) arrives
# here as a real HTTP request for a path with no file behind it. Without
# this line that is a 404 and client-side routing breaks on reload.
location / {
try_files $uri $uri/ /index.html;
}
}