nginx.conf
Rama main · réplica de lectura del repositorio original.
# =============================================================================
# Nginx Configuration for FlowWink (SPA)
# =============================================================================
# Optimized for:
# - Single Page Application routing (all routes -> index.html)
# - Static asset caching with proper headers
# - Gzip compression
# - Security headers
# - Social media crawler detection for SSR meta tags
# =============================================================================
# Detect social media crawlers for SSR meta tag rendering
# These bots need server-rendered OG tags since they don't execute JavaScript
map $http_user_agent $is_social_crawler {
default 0;
"~*facebookexternalhit" 1;
"~*Facebot" 1;
"~*Twitterbot" 1;
"~*LinkedInBot" 1;
"~*WhatsApp" 1;
"~*Slackbot" 1;
"~*Discordbot" 1;
"~*TelegramBot" 1;
"~*Pinterestbot" 1;
"~*Googlebot" 1;
"~*bingbot" 1;
}
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/json
application/xml+rss
application/rss+xml
font/truetype
font/opentype
application/vnd.ms-fontobject
image/svg+xml;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Static assets with long-term caching (hashed filenames from Vite)
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Favicon and other root assets
location ~* \.(ico|png|jpg|jpeg|gif|svg|webp|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# index.html - no cache (always fetch latest)
location = /index.html {
add_header Cache-Control "no-cache, must-revalidate";
try_files $uri =404;
}
# SPA routing with social crawler detection
# Social crawlers get redirected to render-page edge function for SSR meta tags
# Regular users get the SPA
location / {
# Route social crawlers to SSR endpoint for proper OG tags
# IMPORTANT: Replace SUPABASE_PROJECT_URL with your actual Supabase URL
# Example: https://abcdefghijklmnop.supabase.co
if ($is_social_crawler) {
# Uncomment and configure the following lines for production:
# rewrite ^(.*)$ /functions/v1/render-page?path=$1 break;
# proxy_pass SUPABASE_PROJECT_URL;
# proxy_set_header Host $proxy_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;
# For now, fall through to SPA (social sharing will use index.html defaults)
# Configure the proxy_pass above with your Supabase URL to enable SSR
}
# Normal SPA routing
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, must-revalidate";
}
# The MCP gateway at the site's own address — /mcp, the shape every other
# integration uses (mcp.stripe.com, mcp.linear.app/mcp). On Vercel api/mcp.ts
# does this; here it is one proxy_pass to the instance's mcp-server, query
# string and headers (Authorization, Mcp-Session-Id, Accept) intact, with
# buffering OFF so Streamable HTTP's event stream reaches the client as it
# is written. Without it the gateway is still reachable at
# SUPABASE_PROJECT_URL/functions/v1/mcp-server, which the admin shows.
# IMPORTANT: Replace SUPABASE_PROJECT_URL with your actual Supabase URL
location /mcp {
# Uncomment and configure for production:
# rewrite ^/mcp(/.*)?$ /functions/v1/mcp-server$1 break;
# proxy_pass SUPABASE_PROJECT_URL;
# proxy_http_version 1.1;
# proxy_set_header Host $proxy_host;
# proxy_set_header X-Forwarded-Host $host;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_buffering off;
# proxy_read_timeout 150s;
# add_header X-FlowWink-MCP proxy always;
return 404;
}
# Proxy sitemap.xml to Supabase edge function
# IMPORTANT: Replace SUPABASE_PROJECT_URL with your actual Supabase URL
# Example: https://abcdefghijklmnop.supabase.co
location = /sitemap.xml {
# Uncomment and configure for production:
# proxy_pass SUPABASE_PROJECT_URL/functions/v1/sitemap-xml;
# proxy_set_header Host $proxy_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 X-Forwarded-Host $host;
# proxy_cache_valid 200 1h;
# Fallback: return empty sitemap until proxy is configured
default_type application/xml;
return 200 '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"></urlset>';
}
# The blog's RSS feed (the archive's RSS button) — api/blog-rss.ts on Vercel.
# Pass the site's own address as base_url, or every link names the functions host.
location = /api/blog-rss {
# Uncomment and configure for production:
# rewrite ^ /functions/v1/blog-rss?base_url=$scheme://$host&$args break;
# proxy_pass SUPABASE_PROJECT_URL;
# proxy_set_header Host $proxy_host;
# proxy_set_header apikey SUPABASE_ANON_KEY;
# proxy_set_header Authorization "Bearer SUPABASE_ANON_KEY";
# Fallback: an empty feed until the proxy is configured
default_type application/rss+xml;
return 200 '<?xml version="1.0" encoding="UTF-8"?>\n<rss version="2.0"><channel><title>Blog</title><link>https://$host/blog</link><description></description></channel></rss>';
}
# Proxy robots.txt (optional: can also be a static file)
location = /robots.txt {
default_type text/plain;
return 200 'User-agent: *\nAllow: /\nSitemap: https://$host/sitemap.xml\n';
}
# Health check endpoint for container orchestration
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}