ai-chatbot/next.config.ts

164 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { NextConfig } from "next";
function resolvePascalEditorFrameSource() {
const rawUrl = (
process.env.NEXT_PUBLIC_PASCAL_EDITOR_URL ||
process.env.PASCAL_EDITOR_URL ||
""
).trim();
if (!rawUrl || rawUrl.startsWith("/")) {
return null;
}
try {
const url = new URL(rawUrl);
return url.protocol === "http:" || url.protocol === "https:"
? url.origin
: null;
} catch {
return null;
}
}
function resolvePascalEditorFrameSources(isDev: boolean) {
const sources = ["'self'", resolvePascalEditorFrameSource()];
if (isDev) {
sources.push("http://localhost:3002", "http://127.0.0.1:3002");
}
return sources.filter(Boolean).join(" ");
}
function resolveDevMediaOrigins(isDev: boolean) {
if (!isDev) return "";
const origins = [process.env.S3_PUBLIC_URL, process.env.S3_ENDPOINT].flatMap((raw) => {
if (!raw) return [];
try {
const url = new URL(raw);
return url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)
? [url.origin]
: [];
} catch {
return [];
}
});
return [...new Set(origins)].join(" ");
}
const nextConfig: NextConfig = {
distDir: process.env.NEXT_DIST_DIR?.trim() || ".next",
poweredByHeader: false,
allowedDevOrigins: ["127.0.0.1"],
// 禁用 cacheComponents 以支持 dynamic = "force-dynamic"
// cacheComponents: true,
// 外部包配置(Next.js 16+)
serverExternalPackages: ["sharp"],
outputFileTracingIncludes: {
"/api/canvas/generation-tasks": ["./services/smart-edit/analyze.py", "./services/smart-edit/separate.py", "./services/smart-edit/font.py"],
"/api/canvas/smart-edit/fonts/[id]": ["./services/smart-edit/font.py"],
"/api/canvas/smart-edit/capabilities": ["./services/smart-edit/analyze.py", "./services/smart-edit/separate.py", "./services/smart-edit/font.py"],
},
transpilePackages: ["@sparkjsdev/spark"],
typescript: {
ignoreBuildErrors: process.env.NEXT_IGNORE_BUILD_TYPECHECK === "1",
},
// 实验性功能:增加代理请求体大小限制以支持大文件上传(视频等)
experimental: {
proxyClientMaxBodySize: "500mb", // 代理/中间件请求体限制
},
images: {
dangerouslyAllowLocalIP: false,
contentDispositionType: "attachment",
remotePatterns: [
{
hostname: "avatar.vercel.sh",
},
{
protocol: "https",
//https://nextjs.org/docs/messages/next-image-unconfigured-host
hostname: "*.public.blob.vercel-storage.com",
},
{
// 支持 HTTP 协议的 MinIO / S3(本地或内网部署)
protocol: "http",
hostname: "**", // 允许所有 HTTP 域名(MinIO 等)
},
{
// 支持 HTTPS 协议的 S3 / MinIO(公网部署)
protocol: "https",
hostname: "**", // 允许所有 HTTPS 域名
},
],
},
// HTTP 安全头
async headers() {
const isDev = process.env.NODE_ENV === "development";
const frameSources = resolvePascalEditorFrameSources(isDev);
const devMediaOrigins = resolveDevMediaOrigins(isDev);
const csp = [
"default-src 'self'",
// Next.js App Router/RSC emits inline bootstrap scripts in production.
// WebAssembly is required by in-browser 3D decoders (Spark SPZ, KTX2/Basis).
`script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'${isDev ? " 'unsafe-eval'" : ""}`,
// Next.js 需要 unsafe-inline 用于样式 (CSS-in-JS / Tailwind)
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob: https: http:",
"font-src 'self' data:",
// Spark bundles its WASM decoder as a data: URL and initializes it via fetch().
`connect-src 'self' data: https: wss: ${devMediaOrigins}`,
`media-src 'self' blob: https: ${devMediaOrigins}`,
`frame-src ${frameSources}`,
"worker-src 'self' blob:",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
].join("; ");
return [
{
source: "/(.*)",
headers: [
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
{
key: "Strict-Transport-Security",
value: "max-age=31536000; includeSubDomains; preload",
},
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=()",
},
{ key: "Content-Security-Policy", value: csp },
],
},
// 对需要嵌入的页面放宽 frame 限制
{
source: "/worlds/share/:path*",
headers: [
{
key: "X-Frame-Options",
value: "ALLOW-FROM https://www.risesail.com",
},
],
},
];
},
async redirects() {
return [
{
source: "/favicon.ico",
destination: "/api/favicon",
permanent: false,
},
{
source: "/apple-touch-icon.png",
destination: "/api/favicon",
permanent: false,
},
];
},
};
export default nextConfig;