Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b02c6944f | ||
|
|
683fd37b08 | ||
|
|
14c6183aa2 |
@@ -96,7 +96,14 @@ NEXT_PUBLIC_API_ORIGIN=http://localhost:3001
|
||||
```
|
||||
|
||||
The API loads `DATABASE_URL`, `SESSION_SECRET`, `WEB_ORIGIN`, and optional
|
||||
`PORT` (default `3001`). The web app only needs `NEXT_PUBLIC_API_ORIGIN`.
|
||||
`PORT` (default `3001`). `WEB_ORIGIN` is comma-separated — list every origin the
|
||||
app is reached under, or credentialed fetches from the missing ones fail CORS.
|
||||
|
||||
The web app needs no API URL of its own: the browser derives it from the page it
|
||||
loaded (same host on port `3001` over plain HTTP, or the same-origin `/api` path
|
||||
behind a TLS proxy). Set `NEXT_PUBLIC_API_ORIGIN` (dev) or `API_ORIGIN` (deploy,
|
||||
read at request time) only to override that — for instance when running the API
|
||||
on a non-default port.
|
||||
|
||||
### 3. Start MySQL
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jorgecuadros/api",
|
||||
"version": "1.0.16",
|
||||
"version": "1.0.17",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
|
||||
+13
-1
@@ -60,7 +60,19 @@ async function bootstrap() {
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
app.enableCors({ credentials: true, origin: process.env.WEB_ORIGIN ?? "http://localhost:3000" });
|
||||
// The same deployment is reached under several origins — the office LAN IP,
|
||||
// the tailnet name, the demo domain — and the browser derives the API origin
|
||||
// from whichever one served the page (apps/web/src/lib/api.ts). So WEB_ORIGIN
|
||||
// is a comma-separated LIST, not a single value. A request whose Origin is
|
||||
// not listed gets no CORS headers and the credentialed fetch fails, so add an
|
||||
// entry when a new way of reaching the app is introduced. Same-origin setups
|
||||
// (web and API behind one proxy) never hit CORS at all.
|
||||
const webOrigins = (process.env.WEB_ORIGIN ?? "http://localhost:3000")
|
||||
.split(",")
|
||||
.map((o) => o.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
app.enableCors({ credentials: true, origin: webOrigins });
|
||||
|
||||
const port = process.env.PORT ? Number(process.env.PORT) : 3001;
|
||||
await app.listen(port);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jorgecuadros/web",
|
||||
"version": "1.0.16",
|
||||
"version": "1.0.17",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 4500",
|
||||
|
||||
@@ -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 ??
|
||||
|
||||
@@ -132,8 +132,12 @@ services:
|
||||
dns_search:
|
||||
- ${TAILNET_SUFFIX:-tail01aa2.ts.net}
|
||||
environment:
|
||||
# Public API URL the browser calls (injected at runtime, see layout.tsx).
|
||||
API_ORIGIN: ${API_ORIGIN:?API_ORIGIN must be set}
|
||||
# OPTIONAL override of the API URL the browser calls (injected at runtime,
|
||||
# see layout.tsx). Leave it unset: the browser then derives the origin
|
||||
# from the page it loaded — same host on port 3001 over plain HTTP, or
|
||||
# /api behind a TLS-terminating proxy. Set it only when the API really
|
||||
# lives on a different host than the web app.
|
||||
API_ORIGIN: ${API_ORIGIN:-}
|
||||
ports:
|
||||
- "${WEB_PORT:-3000}:3000"
|
||||
depends_on:
|
||||
|
||||
@@ -8,10 +8,21 @@
|
||||
APP_TAG=latest
|
||||
|
||||
# --- Public URLs (what the end user's BROWSER hits) ---------------------------
|
||||
# API_ORIGIN is injected into the web app at runtime and used for browser fetches
|
||||
# + document download links, so it must be browser-reachable (not swarm-internal).
|
||||
# WEB_ORIGIN is the web app's own public origin; the API allows it via CORS.
|
||||
API_ORIGIN=http://192.168.4.212:3001
|
||||
# API_ORIGIN is OPTIONAL and normally left unset. The browser derives the API
|
||||
# origin from the page it loaded (apps/web/src/lib/api.ts): same host on port
|
||||
# 3001 over plain HTTP, or the same-origin /api path when the page is served
|
||||
# over https by a TLS-terminating proxy that maps /api to the API. That is what
|
||||
# lets the same deployment move — tailnet, office LAN, demo domain — untouched.
|
||||
# Set it only when the API genuinely lives on a different host than the web app;
|
||||
# it is used for browser fetches AND document download links, so it must be
|
||||
# browser-reachable (never a swarm-internal name).
|
||||
#API_ORIGIN=http://192.168.4.212:3001
|
||||
#
|
||||
# WEB_ORIGIN is the list of public origins the web app is reached under; the API
|
||||
# allows them via CORS. COMMA-SEPARATED — one deployment is reachable under
|
||||
# several origins (LAN IP, tailnet name, demo domain) and a credentialed fetch
|
||||
# from an origin missing here gets no CORS headers and fails. A same-origin
|
||||
# setup (web + API behind one proxy) never hits CORS at all.
|
||||
WEB_ORIGIN=http://192.168.4.212:3000
|
||||
|
||||
# Published ports on the swarm host.
|
||||
|
||||
@@ -92,8 +92,12 @@ services:
|
||||
labels:
|
||||
io.jorgecuadros.role: "web"
|
||||
environment:
|
||||
# Public API URL the browser calls (injected at runtime, see layout.tsx).
|
||||
API_ORIGIN: ${API_ORIGIN:?API_ORIGIN must be set}
|
||||
# OPTIONAL override of the API URL the browser calls (injected at runtime,
|
||||
# see layout.tsx). Leave it unset: the browser then derives the origin
|
||||
# from the page it loaded — same host on port 3001 over plain HTTP, or
|
||||
# /api behind a TLS-terminating proxy. Set it only when the API really
|
||||
# lives on a different host than the web app.
|
||||
API_ORIGIN: ${API_ORIGIN:-}
|
||||
ports:
|
||||
- target: 3000
|
||||
published: ${WEB_PORT:-3000}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jorgecuadros-platform",
|
||||
"version": "1.0.16",
|
||||
"version": "1.0.17",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jorgecuadros/database",
|
||||
"version": "1.0.16",
|
||||
"version": "1.0.17",
|
||||
"private": true,
|
||||
"main": "generated/client/index.js",
|
||||
"types": "generated/client/index.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user