I once shipped a frontend with http://localhost:8000/api hardcoded in the axios client. It worked perfectly — on my machine. The production build called localhost on every user's browser, failed silently, and I spent an evening learning about CORS before realizing the actual bug was a string literal.
The rule now: the API base URL comes from exactly one env var, with a sane fallback. Nothing else in the frontend knows the backend's address.
// .env.example
VITE_API_URL=http://localhost:8000
const client = isDemoMode() ? createDemoClient() : axios.create({
baseURL: `${import.meta.env.VITE_API_URL || 'http://localhost:8000'}/api`,
headers: {
Accept: 'application/json',
},
});
Three decisions packed into that one line:
import.meta.env.VITE_API_URLis the source of truth. Every environment — local dev, staging, production — sets its own value. The frontend code never changes between deploys; only the env file does.The
|| 'http://localhost:8000'fallback means a fresh clone still runs. Nobody should have to create.envbefore the app boots — a new developer (or me, six months later) should be able tonpm run devand see the login page hitting the local API immediately. The fallback is localhost because that's where the backend runs by default./apiis appended by the client, not the env var. The env var names the host, the client names the prefix. If I ever version the API (/api/v2), I change it in one place, not in every deployment's env file.
The .env.example in the repo documents both knobs — VITE_API_URL and VITE_DEMO_MODE — with comments explaining what each does and what the defaults assume. The real .env is gitignored; the example is the contract. Anyone cloning the repo knows exactly which variables exist and which are optional.
One Vite-specific gotcha for Laravel devs used to .env files on the backend: only variables prefixed with VITE_ are exposed to the frontend. API_URL alone won't work — import.meta.env.API_URL will be undefined and your fallback will silently take over everywhere, including production. Name it VITE_API_URL from the start and you'll never chase that ghost.
The demo-mode branch above (isDemoMode() ? createDemoClient() : ...) is a separate story — it's what lets the public demo run with no backend at all. But the env-var pattern is what makes the real deployments boring, and boring deployments are the goal.
Small discipline, big payoff — it's one of the patterns baked into my Laravel + React SaaS Starter Kit: https://kamranofficial.gumroad.com/l/mhekoig — $19 one-time.
Top comments (0)