[build] # 构建命令 command = "npm run build --silent" # 发布目录:仅暴露构建后的 dist 输出 publish = "dist" # Functions目录 functions = "netlify/functions" # Edge Functions 目录 edge_functions = "netlify/edge-functions" [build.environment] # 固定 Node 版本,避免二进制依赖(如 sharp)在 Node 22 下编译耗时 NODE_VERSION = "20.12.2" # 安装时减少无关输出与提示 NPM_FLAGS = "--no-audit --no-fund --omit=optional" # 重定向和重写规则 [[redirects]] # Giffgaff eSIM页面 - 模块化版本 from = "/giffgaff" to = "/src/giffgaff/giffgaff_modular.html" status = 200 [[redirects]] # Simyo eSIM页面 - 模块化版本 from = "/simyo" to = "/src/simyo/simyo_modular.html" status = 200 [[redirects]] # 根路径重定向到选择页面 from = "/" to = "/index.html" status = 200 # API代理重定向(用于解决CORS问题) # 通用 Simyo API 代理 (webapi - 新版API) [[redirects]] from = "/api/simyo/*" to = "https://appapi.simyo.nl/webapi/api/v1/:splat" status = 200 force = true headers = {X-Forwarded-Host = "appapi.simyo.nl"} # v2 API 代理(用于特定端点如可用验证方式查询) [[redirects]] from = "/api/simyo/esim.availableValidationMethods" to = "https://appapi.simyo.nl/webapi/api/v2/esim.availableValidationMethods" status = 200 force = true headers = {X-Forwarded-Host = "appapi.simyo.nl"} # 保留旧版 simyoapi 路径的兼容性(如果需要) [[redirects]] from = "/api/simyo/legacy/*" to = "https://appapi.simyo.nl/simyoapi/api/v1/:splat" status = 200 force = true headers = {X-Forwarded-Host = "appapi.simyo.nl"} [[headers]] # 限制 Simyo 代理仅允许指定站点跨域访问 for = "/api/simyo/*" [headers.values] Access-Control-Allow-Origin = "https://esim.cosr.eu.org" Access-Control-Allow-Headers = "Content-Type, Authorization" Access-Control-Allow-Methods = "GET, POST, OPTIONS" Vary = "Origin" # 将 /bff/* 映射到 Edge Function bff-proxy [[edge_functions]] path = "/bff/*" function = "bff-proxy" # Markdown for Agents:检测 Accept: text/markdown 请求 [[edge_functions]] path = "/" function = "markdown-negotiation" [[redirects]] from = "/api/giffgaff/*" to = "https://api.giffgaff.com/:splat" status = 200 force = true headers = {X-Forwarded-Host = "api.giffgaff.com"} [[headers]] for = "/api/giffgaff/*" [headers.values] # giffgaff 直连不受此限制,保持通用放行 Access-Control-Allow-Origin = "*" Access-Control-Allow-Headers = "Content-Type, Authorization" Access-Control-Allow-Methods = "GET, POST, OPTIONS" [[redirects]] from = "/api/giffgaff-id/*" to = "https://id.giffgaff.com/:splat" status = 200 force = true headers = {X-Forwarded-Host = "id.giffgaff.com"} [[headers]] for = "/api/giffgaff-id/*" [headers.values] Access-Control-Allow-Origin = "*" Access-Control-Allow-Headers = "Content-Type, Authorization" Access-Control-Allow-Methods = "GET, POST, OPTIONS" [[redirects]] from = "/api/giffgaff-public/*" to = "https://publicapi.giffgaff.com/:splat" status = 200 force = true headers = {X-Forwarded-Host = "publicapi.giffgaff.com"} [[headers]] for = "/api/giffgaff-public/*" [headers.values] Access-Control-Allow-Origin = "*" Access-Control-Allow-Headers = "Content-Type, Authorization" Access-Control-Allow-Methods = "GET, POST, OPTIONS" # favicon 根路径重定向(浏览器默认请求 /favicon.ico) [[redirects]] from = "/favicon.ico" to = "/src/assets/favicon.ico" status = 200 # OG 社交分享图片重定向 [[redirects]] from = "/og-image.png" to = "/src/assets/og-image.png" status = 200 # 处理SPA路由(兜底 404) [[redirects]] from = "/*" to = "/index.html" status = 404 # 静态资源长期缓存(CSS/JS/字体/图片) [[headers]] for = "/src/styles/*" [headers.values] Cache-Control = "public, max-age=31536000, immutable" [[headers]] for = "/src/js/*" [headers.values] Cache-Control = "public, max-age=31536000, immutable" [[headers]] for = "/src/assets/*" [headers.values] Cache-Control = "public, max-age=31536000, immutable" [[headers]] for = "/dist/*" [headers.values] Cache-Control = "public, max-age=31536000, immutable" # HTML 页面:短缓存 + stale-while-revalidate [[headers]] for = "/*.html" [headers.values] Cache-Control = "public, max-age=300, stale-while-revalidate=86400" # 安全头部 [[headers]] for = "/*" [headers.values] X-Frame-Options = "DENY" X-XSS-Protection = "1; mode=block" X-Content-Type-Options = "nosniff" Referrer-Policy = "strict-origin-when-cross-origin" # 生产 CSP:移除 unsafe-inline,基于 nonce/hash 的策略(页面模板需注入 nonce) # 如暂无模板注入机制,可先允许外链受信源并避免内联脚本 Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://www.googletagmanager.com https://www.google.com https://www.gstatic.com https://browser.sentry-cdn.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.googleapis.com; img-src 'self' data: https:; connect-src 'self' https://qrcode.show https://api.qrserver.com https://appapi.simyo.nl https://api.giffgaff.com https://id.giffgaff.com https://publicapi.giffgaff.com https://www.google.com https://www.gstatic.com https://*.sentry.io https://cdn.jsdelivr.net https://browser.sentry-cdn.com; font-src 'self' data: https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.gstatic.com; manifest-src 'self';" # Agent 发现 Link 响应头 (RFC 8288) [[headers]] for = "/" [headers.values] Link = "; rel=\"api-catalog\", ; rel=\"oauth-protected-resource\", ; rel=\"mcp-server-card\", ; rel=\"agent-skills\"" # Markdown for Agents 支持头部 [[headers]] for = "/" [headers.values] Vary = "Accept" [[headers]] for = "/manifest.webmanifest" [headers.values] Content-Type = "application/manifest+json; charset=utf-8"