BizFirst Deployment Models V2
Chapter 1 Concepts

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 at https://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, or grippingly.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.
graph TD Browser["Browser
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.