File size: 2,377 Bytes
a282d4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Standalone output for optimized Docker/HF production builds
  output: "standalone",

  // Compress responses
  compress: true,

  // Image optimization
  images: {
    domains: ["github.com"],
    formats: ["image/avif", "image/webp"],
  },

  // Security headers
  async headers() {
    const apiUrl = process.env.NEXT_PUBLIC_API_URL || "";
    const wsUrl = apiUrl
      ? apiUrl.replace(/^https/, "wss").replace(/^http/, "ws")
      : "";

    // CSP connect-src: allow same-origin + explicit API URL if set
    const connectSrc = ["'self'", apiUrl, wsUrl]
      .filter(Boolean)
      .join(" ");

    return [
      {
        source: "/(.*)",
        headers: [
          { key: "X-Content-Type-Options",  value: "nosniff" },
          { key: "X-Frame-Options",          value: "SAMEORIGIN" }, // HF embeds in iframe
          { key: "X-XSS-Protection",         value: "1; mode=block" },
          { key: "Referrer-Policy",          value: "strict-origin-when-cross-origin" },
          { key: "Permissions-Policy",       value: "camera=(), microphone=(), geolocation=()" },
          {
            key: "Content-Security-Policy",
            value: [
              "default-src 'self'",
              "script-src 'self' 'unsafe-eval' 'unsafe-inline'",
              "style-src 'self' 'unsafe-inline'",
              "img-src 'self' data: https: blob:",
              "font-src 'self' data:",
              `connect-src ${connectSrc} ws: wss:`,
              "worker-src 'self' blob:",
            ].join("; "),
          },
        ],
      },
    ];
  },

  // API proxy rewrites
  // When NEXT_PUBLIC_API_URL is set: proxy to external backend
  // When empty (HF mode): Nginx handles /api/* routing internally
  async rewrites() {
    const apiUrl = process.env.NEXT_PUBLIC_API_URL;
    if (!apiUrl) {
      // HF mode: no rewrites needed — Nginx routes /api/* to FastAPI
      return [];
    }
    return [
      {
        source: "/api/:path*",
        destination: `${apiUrl}/api/:path*`,
      },
    ];
  },

  // Webpack optimizations
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.resolve.fallback = {
        ...config.resolve.fallback,
        fs: false,
        net: false,
        tls: false,
      };
    }
    return config;
  },
};

export default nextConfig;