Inline App Server
Deep dive: the React app is served from the same origin as its API — so the browser can talk to the API using relative paths, and nothing about the deployment needs to know a specific domain name in advance.
What "Same Origin" Buys You
When the static files for a React app and the API endpoints it calls are both reachable under the
same scheme+host+port — for example, everything under https://qa.grippingly.com — the
browser treats every request as same-origin. Same-origin requests:
- Need no CORS configuration. The browser's cross-origin protections simply don't apply — same-origin requests were never restricted in the first place.
- Can use relative paths. A fetch to
/api/v1/...from a page loaded athttps://qa.grippingly.com/flowstudio/automatically resolves against that page's own origin — the browser fills in the scheme and host for you. - Work identically on every same-origin deployment. Because nothing about the
request depends on a specific hardcoded hostname, the exact same compiled bundle behaves correctly
whether it's reached via
qa.grippingly.com,staging.grippingly.com, orgrippingly.com— see Chapter 2.4 for exactly how that resolution works.
How It's Typically Wired
"Inline" doesn't necessarily mean the API server process itself serves the static files (though it can) — the important part is that requests to the React app's HTML/JS/CSS and requests to the API's endpoints both terminate at the same public origin. Common ways to achieve that:
- The API server hosts the compiled
dist/folder as static files, alongside its own/api/*routes. - A reverse proxy (nginx, YARP, IIS, etc.) sits in front of both the API process and a static file
host, and routes by path — e.g.
/api/*to the API, everything else to the React app's static files — while presenting one public hostname.
https://qa.grippingly.com/flowstudio/"] -->|"GET /flowstudio/*
(static assets)"| Proxy["Reverse proxy / API server
https://qa.grippingly.com"] Browser -->|"fetch('/api/v1/...')
(relative — same origin)"| Proxy Proxy -->|"static files"| Static["React dist/ output"] Proxy -->|"/api/* routes"| API["Flow API"]
One public origin, two things behind it. The browser never needs to know they're separate processes.
What This Means for Configuration
No CORS setup
The API doesn't need an
AllowedOrigins list for the frontend — same-origin requests aren't subject to CORS in
the first place.
No per-domain rebuild
The same build can move from QA to
staging to production without touching VITE_API_BASE_URL, because the resolution
happens against whatever origin the browser is currently on.
Leave the base URL empty
VITE_API_BASE_URL can be
left unset — the shared calculateApiUrl() helper falls back to the current page's own
origin, and the individual API client endpoint constants already carry their own
/api/v1/... path prefixes.
Reverse proxy still needs to route /api/*
"No config in the frontend" doesn't mean
"no config anywhere" — whatever serves the origin (nginx, YARP, the API server itself) still has to
actually route /api/* requests to the API. See
Chapter 3.2 for the classic
"404 on /api/*" failure mode.