From bc688e5563511fd58594ce4554d1f09c093003d6 Mon Sep 17 00:00:00 2001 From: johnny2211 Date: Sun, 18 Jan 2026 19:08:17 +0100 Subject: [PATCH] Fix nginx: exclude /uploads/ from static asset caching Problem: GIF files in /uploads/gifs/ were returning 404 errors even though they existed on the backend server. Videos in /uploads/videos/ worked correctly. Root cause: nginx location block order issue - Line 64: Regex location ~* \.(gif|...) matched ALL .gif files - This tried to serve GIFs from frontend dist/ directory (404) - Line 49: /uploads/ proxy never executed for .gif files The regex location block had higher priority than the prefix location block, causing nginx to look for GIFs in the wrong directory. Solution: Exclude /uploads/ path from static asset caching regex - Changed regex from: ~* \.(js|css|...|gif|...)$ - To: ~* ^/(?\!uploads/).*\.(js|css|...|gif|...)$ - This uses negative lookahead to skip files under /uploads/ - Now /uploads/*.gif files are proxied to backend correctly Also moved the static assets location block BEFORE the catch-all location / block for better clarity (doesn't affect behavior). Testing: - nginx -t: configuration syntax OK - Docker container starts successfully - /uploads/videos/*.mp4 still work (proxied to backend) - /uploads/gifs/*.gif now work (proxied to backend) - Frontend static assets still cached (served from dist/) This fixes the 404 errors for GIF preview images on production. Co-Authored-By: Auggie --- nginx.conf | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 nginx.conf diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..5d440dd --- /dev/null +++ b/nginx.conf @@ -0,0 +1,70 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + # Performance + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; + + server { + listen 5173; + server_name _; + + # Frontend static files + root /app/packages/frontend/dist; + index index.html; + + # API proxy to backend + location /api/ { + proxy_pass http://127.0.0.1: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 X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } + + # Uploads proxy to backend + location /uploads/ { + proxy_pass http://127.0.0.1:3000; + 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; + } + + # Cache static assets (but NOT /uploads/* - those are proxied to backend) + location ~* ^/(?!uploads/).*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Frontend SPA routing - serve index.html for all non-file requests + location / { + try_files $uri $uri/ /index.html; + } + } +} +