102 lines
4.3 KiB
Nginx Configuration File
102 lines
4.3 KiB
Nginx Configuration File
server {
|
|
listen 80;
|
|
server_name _;
|
|
|
|
root /usr/share/nginx/html;
|
|
index index.html;
|
|
|
|
# CV / multipart uploads (MAX_PDF_SIZE_MB is 10; leave headroom for form fields).
|
|
client_max_body_size 25m;
|
|
|
|
# Security headers on every response.
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header X-Frame-Options DENY always;
|
|
add_header Referrer-Policy strict-origin-when-cross-origin always;
|
|
|
|
# Same-origin API proxy. The SPA is built with an empty VITE_API_BASE so
|
|
# fetch('/jobs/fetch') stays on this host. OpenAPI (/docs, /redoc,
|
|
# /openapi.json) is intentionally NOT proxied.
|
|
#
|
|
# Paths that are BOTH React routes (/jobs, /inbox, …) and API prefixes must
|
|
# require a sub-path: otherwise a cold open / refresh of /jobs is stolen by
|
|
# the proxy and returns a FastAPI 404 instead of index.html.
|
|
|
|
# SPA page roots that also prefix API calls — sub-path required.
|
|
location ~ ^/(jobs|inbox|pipeline|tasks|assessments|offers|managers|analytics|notifications)/ {
|
|
proxy_pass http://backend-api:8000;
|
|
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 Authorization $http_authorization;
|
|
proxy_connect_timeout 10s;
|
|
proxy_send_timeout 120s;
|
|
proxy_read_timeout 120s;
|
|
proxy_request_buffering off;
|
|
}
|
|
|
|
# API-only prefixes (no SPA page at the bare path).
|
|
location ~ ^/(health|users|roles|permissions|permission-tags|email|job|candidate|notes|interview|feedback|activity|org-settings|saved-searches|search|documents|sheet|s3|forms)(/|$) {
|
|
proxy_pass http://backend-api:8000;
|
|
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 Authorization $http_authorization;
|
|
proxy_connect_timeout 10s;
|
|
proxy_send_timeout 120s;
|
|
proxy_read_timeout 120s;
|
|
proxy_request_buffering off;
|
|
}
|
|
|
|
# One SPA at `/`. Without this, /auth/confirm-email (a router path, not a
|
|
# file) 404s when a confirmation email link is opened cold.
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
|
|
# Hashed filenames, so they can be cached hard. A miss after a rebuild is a
|
|
# stale tab (old import() hash) — 404 + immutable would pin that miss for a
|
|
# year, so JS falls through to a one-shot reload of index.html.
|
|
location /assets/ {
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header X-Frame-Options DENY always;
|
|
add_header Referrer-Policy strict-origin-when-cross-origin always;
|
|
|
|
location ~ \.js$ {
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header X-Frame-Options DENY always;
|
|
add_header Referrer-Policy strict-origin-when-cross-origin always;
|
|
try_files $uri @stale_js;
|
|
}
|
|
}
|
|
|
|
location @stale_js {
|
|
default_type application/javascript;
|
|
add_header Cache-Control "no-store" always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
return 200 "try{if(!sessionStorage.getItem('tf-chunk-reload')){sessionStorage.setItem('tf-chunk-reload','1');location.reload();}else{sessionStorage.removeItem('tf-chunk-reload');}}catch(e){location.reload();}";
|
|
}
|
|
|
|
# index.html must never be cached, or a redeploy keeps serving the old asset hashes.
|
|
location = /index.html {
|
|
etag off;
|
|
if_modified_since off;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
|
|
add_header Pragma "no-cache" always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header X-Frame-Options DENY always;
|
|
add_header Referrer-Policy strict-origin-when-cross-origin always;
|
|
}
|
|
|
|
gzip on;
|
|
gzip_min_length 1024;
|
|
gzip_types text/css text/javascript application/javascript application/json image/svg+xml;
|
|
}
|