import type { NextConfig } from "next"; const nextConfig: NextConfig = { // The Docker image runs the self-contained server from .next/standalone (see Dockerfile). output: process.env.NEXT_OUTPUT === "standalone" ? "standalone" : undefined, // ssh2 has optional native bindings and must be loaded by Node, not bundled. serverExternalPackages: ["ssh2"], // Container clients request /v2/ and /v2//blobs/uploads/ with a trailing slash and don't // follow redirects for uploads, so the built-in redirect is replaced by one that skips /v2. skipTrailingSlashRedirect: true, // The OAuth consent page must not be framed, or another site could trick a click on "Authorize". async headers() { return [ { source: "/oauth/authorize", headers: [ { key: "X-Frame-Options", value: "DENY" }, { key: "Content-Security-Policy", value: "frame-ancestors 'none'" }, ], }, ]; }, // Any page as Markdown, for agents: Accept: text/markdown, or ?format=md (lib/markdown-views.ts). async rewrites() { const md = "/api/markdown/:path*"; return { beforeFiles: [ { source: "/", has: [{ type: "header", key: "accept", value: "(?:.*,\\s*)?text/markdown.*" }], destination: "/api/markdown" }, { source: "/", has: [{ type: "query", key: "format", value: "md" }], destination: "/api/markdown" }, { source: "/:path((?!api/|_next/|v2(?:/|$)).+)", has: [{ type: "header", key: "accept", value: "(?:.*,\\s*)?text/markdown.*" }], destination: md }, { source: "/:path((?!api/|_next/|v2(?:/|$)).+)", has: [{ type: "query", key: "format", value: "md" }], destination: md }, ], afterFiles: [], fallback: [], }; }, async redirects() { return [{ source: "/:path((?!v2(?:/|$)).+)/", destination: "/:path", permanent: true }]; }, experimental: { authInterrupts: true, serverActions: { // Large enough for file uploads through the web UI. bodySizeLimit: "25mb", }, }, }; export default nextConfig;