# ────────────────────────────────────────────── # Nūr — Muslim Companion · Nginx SPA Config # Target: moslem02.falahos.my (Cloudflare proxied) # ────────────────────────────────────────────── upstream backend_prayer { # Placeholder for future API upstream (e.g. aladhan.com proxy) keepalive 32; } server { listen 80; listen [::]:80; server_name moslem02.falahos.my; # ── Cloudflare real-ip ───────────────────── # Cloudflare sends visitor IP via CF-Connecting-IP header. # Uncomment and populate with current CF IP ranges: # https://www.cloudflare.com/ips-v4 / ips-v6 # real_ip_header CF-Connecting-IP; # real_ip_recursive on; # set_real_ip_from 173.245.48.0/20; # set_real_ip_from 103.21.244.0/22; # … (keep current by syncing from cloudflare.com/ips-v4) # ── Static root ──────────────────────────── root /var/www/nur-muslim-companion; index index.html; error_page 404 =200 /index.html; # ── Gzip ─────────────────────────────────── gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 5; gzip_min_length 512; gzip_types text/html text/plain text/css text/javascript application/javascript application/json application/manifest+json image/svg+xml image/x-icon font/woff2; # ── Security headers ─────────────────────── add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "0" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # HSTS — only enable once TLS is confirmed working via Cloudflare # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # ── SPA fallback ─────────────────────────── # All routes serve index.html; actual 404s are impossible. # index.html is NOT cached so updates reach clients immediately. location / { try_files $uri $uri/ /index.html; # index.html: no-cache (must always fetch fresh version) add_header Cache-Control "no-cache, must-revalidate" always; } # ── Hashed static assets (immutable) ─────── # Vite emits content-hashed filenames: assets/index-abc123.js # These can be cached forever on CDN and browsers alike. location /assets/ { expires 1y; add_header Cache-Control "public, immutable, max-age=31536000" always; access_log off; } # ── Favicon ──────────────────────────────── location = /favicon.svg { expires 7d; add_header Cache-Control "public, max-age=604800" always; access_log off; } # ── PWA manifest ─────────────────────────── location = /manifest.webmanifest { expires 1d; add_header Cache-Control "public, max-age=86400" always; add_header Content-Type "application/manifest+json"; } # ── Service Worker ───────────────────────── # Service-worker script must NOT be cached and must be served # from its own scope (root). Cloudflare bypasses cache for sw.js. location /sw.js { add_header Cache-Control "no-cache, no-store, must-revalidate" always; add_header Service-Worker-Allowed "/"; expires off; access_log off; } # ── PWA app shell (workbox precached) ────── # workbox-*.js, worker-*.js are versioned hashed files: location ~* \.(js|css|woff2)$ { expires 1y; add_header Cache-Control "public, immutable, max-age=31536000" always; } # ── Icon files ───────────────────────────── location ~* \.(png|ico)$ { expires 1y; add_header Cache-Control "public, immutable, max-age=31536000" always; access_log off; } # ── Deny hidden files ────────────────────── location ~ /\. { deny all; access_log off; log_not_found off; } # ── Deny node_modules & src ──────────────── location ~ ^/(node_modules|src)/ { deny all; access_log off; } }