import type { NextConfig } from 'next'; import path from 'path'; import withSerwistInit from '@serwist/next'; // Content-Security-Policy: scoped to what the app actually loads. // - 'unsafe-eval' / 'unsafe-inline' on script-src: Next.js dev runtime and // the inline bootstrap script require these. They can be tightened later // with a nonce-based approach if needed. // - js.stripe.com: Stripe.js (loaded on checkout) // - PostHog assets/connect: web/src/components/PostHogProvider.tsx // - Google Fonts: layout.tsx tags // - Supabase wss: AuthContext realtime + push subscriptions const CSP = [ "default-src 'self'", "script-src 'self' 'unsafe-eval' 'unsafe-inline' https://js.stripe.com https://us-assets.i.posthog.com", "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", "font-src 'self' https://fonts.gstatic.com", "img-src 'self' data: blob: https://*.supabase.co https://cdn.nba.com https://a.espncdn.com", "connect-src 'self' https://*.supabase.co wss://*.supabase.co https://api.stripe.com https://us.i.posthog.com https://us-assets.i.posthog.com", "frame-src https://js.stripe.com https://hooks.stripe.com", "worker-src 'self' blob:", "manifest-src 'self'", "object-src 'none'", "base-uri 'self'", "form-action 'self'", ].join('; '); const nextConfig: NextConfig = { output: 'standalone', turbopack: { root: path.join(__dirname), }, images: { remotePatterns: [], }, poweredByHeader: false, reactStrictMode: true, async headers() { return [ { source: '/(.*)', headers: [ { key: 'Content-Security-Policy', value: CSP }, { key: 'X-Frame-Options', value: 'DENY' }, { key: 'X-Content-Type-Options', value: 'nosniff' }, { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }, { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' }, { key: 'X-DNS-Prefetch-Control', value: 'on' }, { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' }, ], }, // The service worker must be served with no-cache or browsers will pin // an outdated SW for days. Override the global Cache-Control here. { source: '/sw.js', headers: [ { key: 'Cache-Control', value: 'no-cache, no-store, must-revalidate' }, { key: 'Service-Worker-Allowed', value: '/' }, ], }, ]; }, }; const withSerwist = withSerwistInit({ swSrc: 'src/sw.ts', swDest: 'public/sw.js', // Serwist only ships in production builds — dev requests bypass the SW so // hot-reload and source maps work normally. disable: process.env.NODE_ENV === 'development', }); export default withSerwist(nextConfig);