Files
multica/apps/web/next.config.ts
苗大 a90aa92d0c fix(web): resolve API and docs upstreams at runtime (#4840)
* fix(web): resolve upstream URLs at runtime

* fix(web): keep unconfigured upstreams same-origin

* fix(web): restore dev-only localhost fallbacks for API and docs upstreams

`next dev` on a developer machine now falls back to the conventional
http://localhost:8080 backend (honoring BACKEND_PORT) and
http://localhost:4000 docs origin when nothing is configured, so a bare
`pnpm dev:web` keeps proxying out of the box. Builds and the runtime
proxy keep the strict resolvers, so prebuilt images still leave unset
upstreams unproxied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 11:02:46 +08:00

99 lines
3.2 KiB
TypeScript

import type { NextConfig } from "next";
import { config } from "dotenv";
import { resolve } from "path";
import {
resolveDevDocsUrl,
resolveDevRemoteApiUrl,
resolveDocsUrl,
resolveRemoteApiUrl,
} from "./config/runtime-urls";
import { createMDX } from "fumadocs-mdx/next";
// Load root .env so local next.config.ts rewrites see REMOTE_API_URL / DOCS_URL.
// Production requests use proxy.ts runtime rewrites, which read process.env
// when the Next.js server runs instead of baking these URLs at build time.
config({ path: resolve(__dirname, "../../.env") });
// `next dev` falls back to the conventional localhost upstreams; builds use
// the strict resolvers so prebuilt images keep unset upstreams unproxied.
const isDev = process.env.NODE_ENV === "development";
const remoteApiUrl = isDev
? resolveDevRemoteApiUrl(process.env)
: resolveRemoteApiUrl(process.env);
const docsUrl = isDev
? resolveDevDocsUrl(process.env)
: resolveDocsUrl(process.env);
// Parse hostnames from CORS_ALLOWED_ORIGINS so that Next.js dev server
// allows cross-origin HMR / webpack requests (e.g. from Tailscale IPs).
const allowedDevOrigins = process.env.CORS_ALLOWED_ORIGINS
? process.env.CORS_ALLOWED_ORIGINS.split(",")
.map((origin) => {
try {
return new URL(origin.trim()).host;
} catch {
return origin.trim();
}
})
.filter(Boolean)
: undefined;
const nextConfig: NextConfig = {
...(process.env.STANDALONE === "true" ? { output: "standalone" as const } : {}),
transpilePackages: ["@multica/core", "@multica/ui", "@multica/views"],
...(allowedDevOrigins && allowedDevOrigins.length > 0
? { allowedDevOrigins }
: {}),
images: {
formats: ["image/avif", "image/webp"],
qualities: [75, 80, 85],
},
async rewrites() {
return {
// Run before file-system routes so /docs isn't shadowed by the
// [workspaceSlug] dynamic segment.
beforeFiles: docsUrl
? [
{
source: "/docs",
destination: `${docsUrl}/docs`,
},
{
source: "/docs/:path*",
destination: `${docsUrl}/docs/:path*`,
},
]
: [],
afterFiles: remoteApiUrl
? [
{
source: "/api/:path*",
destination: `${remoteApiUrl}/api/:path*`,
},
{
source: "/ws",
destination: `${remoteApiUrl}/ws`,
},
{
source: "/auth/:path*",
destination: `${remoteApiUrl}/auth/:path*`,
},
{
source: "/uploads/:path*",
destination: `${remoteApiUrl}/uploads/:path*`,
},
]
: [],
fallback: [],
};
},
};
// fumadocs-mdx@12 is incompatible with Next 16's Turbopack: its loader fails to
// dynamic-import `.source/source.config.mjs` under the Turbopack Node evaluator
// (see fumadocs#2658). `dev`/`build` scripts pass `--webpack` to opt out.
// Drop the flag once fumadocs-mdx ships a Turbopack-compatible loader.
const withMDX = createMDX() as (config: NextConfig) => NextConfig;
export default withMDX(nextConfig);