feat(deploy): derive the API origin from the page, not from a pinned env var
The browser hard-required API_ORIGIN, so every move of the server — tailnet today, the 192.168.1.0 office LAN later, a temporary demo domain in between — meant editing the deploy env and redeploying. Worse, an http:// API origin on a page served over TLS is blocked outright as mixed active content, which is what broke the demo on https://jorgecuadros.freakma.com. The browser now derives the origin from window.location the way a PHP app would: same host on port 3001 over plain HTTP, or the same-origin /api path under https (the reverse proxy strips the prefix). API_ORIGIN survives as an optional override for a deployment that genuinely splits the two hosts, and SSR still reads process.env because a derived origin is browser-only. WEB_ORIGIN becomes a comma-separated list to match: one deployment is now reached under several origins, and a credentialed fetch from an unlisted one gets no CORS headers and fails. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,19 +8,18 @@ export const metadata = {
|
||||
"Plataforma interna unificada de clientes, servicios y seguros.",
|
||||
};
|
||||
|
||||
// The browser talks to the API cross-origin, so it needs the API URL at
|
||||
// runtime. NEXT_PUBLIC_* would bake it at build time (one URL per image); we
|
||||
// want the URL to come from the deploy .env instead. So read it here on the
|
||||
// server per request and inject it as window.__API_ORIGIN__ (see lib/api.ts).
|
||||
// force-dynamic guarantees process.env is read at request time, never baked
|
||||
// into a static prerender.
|
||||
// API_ORIGIN is an OPTIONAL override, read here on the server per request and
|
||||
// injected as window.__API_ORIGIN__ (see lib/api.ts). NEXT_PUBLIC_* would bake
|
||||
// it at build time (one URL per image); reading it here keeps one image usable
|
||||
// anywhere. Left unset — the normal case — this injects the empty string and
|
||||
// lib/api.ts derives the origin from window.location instead, so the app
|
||||
// follows the server when it moves without an env edit. force-dynamic
|
||||
// guarantees process.env is read at request time, never baked into a static
|
||||
// prerender.
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
const apiOrigin =
|
||||
process.env.API_ORIGIN ??
|
||||
process.env.NEXT_PUBLIC_API_ORIGIN ??
|
||||
"http://localhost:3001";
|
||||
const apiOrigin = process.env.API_ORIGIN ?? "";
|
||||
// Same reason as the API origin: read on the server per request so the built
|
||||
// image is not pinned to one build identity in its client bundle.
|
||||
const build = readBuildInfoFromEnv();
|
||||
|
||||
+16
-5
@@ -82,15 +82,26 @@ import type {
|
||||
UserRow,
|
||||
} from "./types";
|
||||
|
||||
// Resolve the API origin at runtime, not build time. In the browser it comes
|
||||
// from window.__API_ORIGIN__, injected server-side by the root layout from the
|
||||
// deploy .env (API_ORIGIN) — so one built image serves any deployment. On the
|
||||
// server (SSR) read process.env directly. NEXT_PUBLIC_API_ORIGIN stays as the
|
||||
// dev/build fallback.
|
||||
// Resolve the API origin at runtime, not build time — so one built image serves
|
||||
// any deployment and the app follows the box when it moves (tailnet today,
|
||||
// 192.168.1.x office LAN later) with no config change.
|
||||
//
|
||||
// In the browser, derive the origin from the page's own location, the way a PHP
|
||||
// app would. An explicit API_ORIGIN (injected as window.__API_ORIGIN__ by the
|
||||
// root layout) still wins when a deployment genuinely splits the two hosts.
|
||||
// On the server (SSR) read process.env directly — a derived origin is
|
||||
// browser-only, and "/api" is not fetchable server-side.
|
||||
function resolveApiOrigin(): string {
|
||||
if (typeof window !== "undefined") {
|
||||
const injected = (window as { __API_ORIGIN__?: string }).__API_ORIGIN__;
|
||||
if (injected) return injected;
|
||||
const { protocol, hostname } = window.location;
|
||||
// Over TLS the API must share the page's origin or the browser blocks the
|
||||
// call as mixed active content. The reverse proxy maps /api to the API.
|
||||
if (protocol === "https:") return "/api";
|
||||
// Plain HTTP: same host, API port. 3001 is the port the API container
|
||||
// publishes everywhere (deploy/galactus/jorgecuadros-app.compose.yml).
|
||||
return `http://${hostname}:3001`;
|
||||
}
|
||||
return (
|
||||
process.env.API_ORIGIN ??
|
||||
|
||||
Reference in New Issue
Block a user