Files
Your Name d4a2a0ba64 refactor: arch audit Phase 1 -- quick wins
- Token refresh race: add promise dedup mutex to ApiClient.refreshAccessToken()
- CSP header: add Content-Security-Policy to next.config.ts
- Ephemeral Redis: replace new Redis()/publish/quit in clearPauseState() with enrichmentStateService.publishToChannel()
- Server startup order: move health checks before app.listen() into main()
- Dead code: remove updatePodcastProgress() wrapper, OpenAI config block, assets.fanart.tv remote pattern, tailwind.config.js, unused imports (fs, imageLimiter, cycleResult vars)
- Types: move @types/dompurify (FE) and @types/fluent-ffmpeg/@types/node-cron/@types/qrcode/@types/speakeasy (BE) to devDependencies
2026-03-20 15:32:33 -05:00

134 lines
4.5 KiB
TypeScript

import type { NextConfig } from "next";
import bundleAnalyzer from '@next/bundle-analyzer';
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
});
const nextConfig: NextConfig = {
turbopack: {
root: __dirname,
},
// Allow dev origins for local network testing
allowedDevOrigins: [
"http://127.0.0.1:3030",
"http://127.0.0.1",
"127.0.0.1",
"http://localhost:3030",
"http://localhost",
"localhost",
],
images: {
remotePatterns: [
{
protocol: "https",
hostname: "cdn-images.dzcdn.net",
pathname: "/**",
},
{
protocol: "https",
hostname: "e-cdns-images.dzcdn.net",
pathname: "/**",
},
{
protocol: "https",
hostname: "lastfm.freetls.fastly.net",
pathname: "/**",
},
{
protocol: "https",
hostname: "lastfm-img2.akamaized.net",
pathname: "/**",
},
{
protocol: "http",
hostname: "localhost",
port: "3006",
pathname: "/**",
},
{
protocol: "http",
hostname: "127.0.0.1",
port: "3006",
pathname: "/**",
},
{
protocol: "https",
hostname: "assets.pippa.io",
pathname: "/**",
},
{
protocol: "https",
hostname: "is1-ssl.mzstatic.com",
pathname: "/**",
},
],
formats: ["image/avif", "image/webp"],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
minimumCacheTTL: 60 * 60 * 24 * 7, // Cache for 7 days
// SVG optimization disabled for security (prevents XSS via crafted SVGs)
},
reactStrictMode: true,
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=()",
},
{
key: "X-Frame-Options",
value: "DENY",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
{
key: "X-DNS-Prefetch-Control",
value: "on",
},
{
key: "Content-Security-Policy",
value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://cdn-images.dzcdn.net https://e-cdns-images.dzcdn.net https://lastfm.freetls.fastly.net https://lastfm-img2.akamaized.net https://assets.pippa.io https://is1-ssl.mzstatic.com; media-src 'self' blob:; connect-src 'self' ws: wss:; font-src 'self'; frame-ancestors 'none';",
},
],
},
];
},
// Proxy API requests to backend (for Docker all-in-one container)
// Use NEXT_PUBLIC_BACKEND_URL if set (build-time), otherwise default to localhost:3006
// At runtime, Next.js will proxy /api/* requests to the backend
// NOTE: /api/events is excluded -- it uses a dedicated API route (app/api/events/route.ts)
// that properly streams SSE without buffering.
async rewrites() {
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || "http://127.0.0.1:3006";
return [
{
source: "/api/:path((?!events).*)*",
destination: `${backendUrl}/api/:path*`,
},
{
source: "/rest/:path*",
destination: `${backendUrl}/rest/:path*`,
},
{
source: "/health",
destination: `${backendUrl}/health`,
},
];
},
};
export default withBundleAnalyzer(nextConfig);