Connecting a frontend to a backend
You’ve deployed two projects: a frontend and an API. Now the frontend needs to call the API. There are two ways to do it, and one setting that catches almost everyone who picks the second.
Option 1 — call the API from the browser
Section titled “Option 1 — call the API from the browser”The simplest setup. Your frontend fetches the API’s own URL directly:
const res = await fetch(`${import.meta.env.VITE_API_URL}/api/pages/tree`);with the API’s public URL in an environment variable:
VITE_API_URL=https://myapi-ab12cd34.pandastack.appBecause the request comes from a browser on a different origin, your API must send CORS headers. In ASP.NET Core:
builder.Services.AddCors(o => o.AddDefaultPolicy(p => p .WithOrigins("https://app.example.com") .AllowAnyHeader() .AllowAnyMethod()));
app.UseCors();In Express:
app.use(cors({ origin: "https://app.example.com" }));Name your real frontend origin rather than using a wildcard — it’s the difference between “anyone can call this API from any page” and “my app can”.
Option 2 — proxy through your frontend server
Section titled “Option 2 — proxy through your frontend server”If your frontend runs a server of its own — Angular SSR, Next.js, Nuxt, or plain Express — you can forward /api/* to the backend instead. Same-origin requests, so no CORS at all.
This is where the one setting comes in.
import { createProxyMiddleware } from "http-proxy-middleware";
app.use("/api", createProxyMiddleware({ target: process.env.API_BASE_URL, changeOrigin: true, // ← required}));changeOrigin defaults to false. Left alone, your server forwards the browser’s original Host header — so a request meant for your API arrives carrying your frontend’s hostname. PandaStack routes by hostname, so it sends the request back to the frontend, which proxies it again, and the loop is stopped with an error.
Setting changeOrigin: true rewrites the Host header to the target. That one line is the whole fix.
The equivalent option in other tools:
| Tool | Setting |
|---|---|
http-proxy-middleware (Express, Angular SSR) | changeOrigin: true |
Angular proxy.conf.json | "changeOrigin": true |
Vite server.proxy | changeOrigin: true |
Next.js rewrites() | handled for you — no setting needed |
| nginx | proxy_set_header Host $proxy_host; |
| Caddy | reverse_proxy rewrites Host by default |
Option 3 — stay on the internal network
Section titled “Option 3 — stay on the internal network”Apps in the same organization can reach each other without going out to the internet at all. See Internal networking for the hostnames.
Troubleshooting
Section titled “Troubleshooting”Error 1000: DNS points to prohibited IP / dns_loop
Section titled “Error 1000: DNS points to prohibited IP / dns_loop”You’ll see this as a 403 from a Cloudflare error page, naming your backend’s hostname, when the request came through your frontend.
Despite what the message says, there is nothing wrong with your DNS, and there are no A records for you to change — the hostname belongs to PandaStack.
It means your proxy forwarded the wrong Host header. Set changeOrigin: true (see Option 2 above).
Quick way to confirm it — the same request, two different Host headers:
# with the backend's own hostname → workscurl -s -o /dev/null -w "%{http_code}\n" \ -H "Host: myapi-ab12cd34.pandastack.app" \ https://myapi-ab12cd34.pandastack.app/api/health
# with the frontend's hostname → 403curl -s -o /dev/null -w "%{http_code}\n" \ -H "Host: app.example.com" \ https://myapi-ab12cd34.pandastack.app/api/healthIf the first returns 200 and the second 403, your backend is healthy and the Host header is the problem.
CORS errors in the browser console
Section titled “CORS errors in the browser console”You’re on Option 1 and the API isn’t sending CORS headers, or it’s not allowing your frontend’s exact origin. Check that the origin in your CORS policy matches your frontend URL exactly — scheme included, no trailing slash.
The first request after an idle period is slow
Section titled “The first request after an idle period is slow”That’s a cold start. Apps on scale-to-zero shut down when idle and start again on the next request. See Autoscaling & scale-to-zero.