feat(config): resolve the API base at runtime from config.js

This commit is contained in:
LincPay
2026-08-15 17:01:45 +05:30
parent aa1484c285
commit 39ca34be68
4 changed files with 82 additions and 3 deletions
+7
View File
@@ -11,6 +11,13 @@
</head>
<body>
<div id="root"></div>
<!--
Runtime config, loaded before the app and deliberately not a module: a
classic script blocks until it has run, so window.__UDYAM_CONFIG__ is set
before any module code reads it. Served unbundled from the static root so
the API base can be changed on the server without rebuilding.
-->
<script src="/config.js"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+7
View File
@@ -19,6 +19,13 @@ server {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# Runtime config — never cached. It is how the API base is changed without a
# rebuild, and a cached copy would silently pin the old URL for every browser
# that has already loaded the app, with no way to tell from the server side.
location = /config.js {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# The API, same-origin. The bundle is built with VITE_UDYAM_API_BASE_URL set
# to the relative /api/v1/udyam, so it carries no hostname at all and the
# same dist/ serves from any domain. That also puts the API on the UI's own
+30
View File
@@ -0,0 +1,30 @@
/*
* Runtime configuration — edited on the server, not baked into the bundle.
*
* Vite inlines import.meta.env.VITE_* at build time, so a bundle built for one
* backend URL cannot be pointed at another without rebuilding. That is the wrong
* shape for a deployment where the backend goes up first and the UI follows, or
* where the same artifact is promoted from demo to staging. This file is served
* from the static root unbundled, so ops can change the API base and reload.
*
* Precedence, highest first:
* 1. apiBaseUrl below, if it is a non-empty string
* 2. VITE_UDYAM_API_BASE_URL, compiled in at build time
* 3. "/api/v1/udyam" — same-origin, which is what the nginx and httpd configs
* in DEPLOYMENT.md set up
*
* Leave it empty for a same-origin deployment; that is the common case and needs
* no edit. Set it when the API lives on another host:
*
* apiBaseUrl: "https://udyamlinc-api.lincpay.tech/api/v1/udyam"
*
* A cross-origin value also needs that origin in the backend's
* UDYAM_CORS_ALLOWED_ORIGINS, or the browser blocks every call while the API
* itself looks healthy.
*
* This file must never be cached — the server configs send no-store for it.
* Cached, it silently pins the old URL for anyone who has already loaded the app.
*/
window.__UDYAM_CONFIG__ = {
apiBaseUrl: '',
}
+38 -3
View File
@@ -1,8 +1,43 @@
import axios from 'axios'
/** udyamlinc-backend — Spring Boot skeleton, context path /api/v1/udyam. */
export const API_BASE =
import.meta.env.VITE_UDYAM_API_BASE_URL ?? 'http://udyamlinc-backend.lincpay/api/v1/udyam'
declare global {
interface Window {
__UDYAM_CONFIG__?: { apiBaseUrl?: string }
}
}
/**
* Where the API lives — resolved at runtime, not frozen into the bundle.
*
* <p>Precedence, highest first:
*
* 1. `window.__UDYAM_CONFIG__.apiBaseUrl` from `public/config.js`, which is served
* unbundled and can be edited on the server. This is what lets the backend be
* deployed first and the UI pointed at it afterwards, without a rebuild.
* 2. `VITE_UDYAM_API_BASE_URL`, inlined at build time — still useful for `npm run
* dev` against a local backend.
* 3. `/api/v1/udyam` — relative, so the bundle carries no hostname, works from any
* domain, and is same-origin with the API that nginx or httpd proxies. Being
* same-origin also means CORS never applies, which removes the largest single
* source of works-locally-fails-deployed.
*
* <p>An empty or whitespace-only value counts as unset. A deployment that writes
* `apiBaseUrl: ""` into config.js means "use the default", and treating that as a
* real base would send every request to the page's own URL.
*/
function resolveApiBase(): string {
const runtime = window.__UDYAM_CONFIG__?.apiBaseUrl
if (typeof runtime === 'string' && runtime.trim() !== '') {
return runtime.trim().replace(/\/+$/, '')
}
const compiled = import.meta.env.VITE_UDYAM_API_BASE_URL
if (typeof compiled === 'string' && compiled.trim() !== '') {
return compiled.trim().replace(/\/+$/, '')
}
return '/api/v1/udyam'
}
export const API_BASE = resolveApiBase()
/** Short timeout: field devices are on patchy 2G and every screen has a local fallback. */
export const api = axios.create({ baseURL: API_BASE, timeout: 6_000 })